Added angular-librarian for manage library

This commit is contained in:
Lorenzo Iovino 2017-11-14 11:07:08 +01:00
parent 9d174550f5
commit e625f9fd30
44 changed files with 1867 additions and 228 deletions

69
tasks/build.js Normal file
View file

@ -0,0 +1,69 @@
'use strict';
const fs = require('fs-extra');
const ngc = require('@angular/compiler-cli/src/main').main;
const librarianUtils = require('angular-librarian/commands/utilities');
const path = require('path');
const copyGlobs = require('./copy-globs');
const copyToBuild = require('./copy-build');
const inlineResources = require('./inline-resources');
const rollup = require('./rollup');
const colorize = librarianUtils.colorize;
const rootDir = path.resolve(__dirname, '..');
const buildDir = path.resolve(rootDir, 'build');
const distDir = path.resolve(rootDir, 'dist');
const libName = require(path.resolve(rootDir, 'package.json')).name;
const srcDir = path.resolve(rootDir, 'src');
const tscDir = path.resolve(rootDir, 'out-tsc');
const es5Dir = path.resolve(tscDir, 'lib-es5');
const es2015Dir = path.resolve(tscDir, 'lib-es2015');
const runPromise = (message, fn) => {
return function() {
console.info(colorize.colorize(message, 'cyan'));
return fn().then(complete);
};
};
const complete = (depth = 0) => {
const spaces = ' '.repeat(depth);
console.info(colorize.colorize(`${ spaces }> Complete`, 'green'));
};
const compileCode = () => Promise.all([2015, 5].map((type) =>
ngc({ project: path.resolve(rootDir, `tsconfig.es${ type }.json`)})
.then((exitCode) =>
exitCode === 0 ? Promise.resolve() : Promise.reject()
)
));
const copyMetadata = () =>
copyGlobs(['**/*.d.ts', '**/*.metadata.json'], es2015Dir, distDir);
const copyPackageFiles = () =>
copyGlobs(['.npmignore', 'package.json', 'README.md'], rootDir, distDir)
.then(() => {
const contents = fs.readFileSync(path.resolve(distDir, 'package.json'), 'utf8');
return fs.writeFileSync(path.resolve(distDir, 'package.json'), contents.replace('"dependencies":', '"peerDependencies":'));
});
const copySource = () => copyGlobs('**/*', srcDir, buildDir);
const doInlining = () => inlineResources(buildDir, 'src');
const rollupBundles = () => rollup(libName, {
dist: distDir,
es2015: es2015Dir,
es5: es5Dir,
root: rootDir
});
return Promise.resolve()
.then(runPromise('Copying `src` files into `build`', copySource))
.then(runPromise('Inlining resources', doInlining))
.then(runPromise('Compiling code', compileCode))
.then(runPromise('Copying typings + metadata to `dist`', copyMetadata))
.then(runPromise('Generating bundles via rollup', rollupBundles))
.then(runPromise('Copying package files to `dist`', copyPackageFiles))
.catch((error) => {
console.error('\x1b[31m%s\x1b[0m', '> Build failed\n');
console.error(error);
process.exit(1);
});

16
tasks/copy-build.js Normal file
View file

@ -0,0 +1,16 @@
'use strict';
const fs = require('fs-extra');
// copy all src files -> build
const copyToBuild = (buildDir, sourceDir) => {
fs.ensureDirSync(buildDir);
fs.emptyDirSync(buildDir);
fs.copySync(sourceDir, buildDir);
};
module.exports = copyToBuild;
if (!module.parent) {
copyToBuild('./build', './src');
}

35
tasks/copy-globs.js Normal file
View file

@ -0,0 +1,35 @@
'use strict';
const fs = require('fs-extra');
const glob = require('glob');
const path = require('path');
const copy = (globs, from, to) => {
if (typeof globs === 'string') {
globs = [globs];
}
fs.ensureDir(to);
return Promise.all(
globs.map((fileGlob) => copyGlob(fileGlob, from, to))
);
};
const copyGlob = (fileGlob, from, to) => new Promise((resolve, reject) => {
glob(fileGlob, { cwd: from, nodir: true }, (error, files) => {
if (error) reject(error);
files.forEach((file) => {
const origin = path.resolve(from, file);
const destination = path.resolve(to, file);
const contents = fs.readFileSync(origin, 'utf8');
fs.ensureDirSync(path.dirname(destination));
fs.writeFileSync(destination, contents);
});
resolve();
});
});
module.exports = copy;

186
tasks/inline-resources.js Normal file
View file

@ -0,0 +1,186 @@
'use strict';
// original code by the Angular Material 2 team
const fs = require('fs');
const glob = require('glob');
const path = require('path');
const sass = require('node-sass');
const inlineResources = (globs, sourcePrefix) => {
if (typeof globs === 'string') {
globs = [globs];
}
return Promise.all(
globs.map((pattern) => replaceSource(pattern, sourcePrefix))
);
};
const replaceSource = (pattern, sourcePrefix) => {
// pattern is a directory
if (pattern.indexOf('*') === -1) {
pattern = path.join(pattern, '**', '*');
}
return new Promise((resolve, reject) => {
glob(pattern, {}, (error, files) => {
if (error) reject(Error);
files.filter((name) => /\.ts$/.test(name)).forEach((filePath) => {
try {
inlineFileResources(filePath, sourcePrefix);
} catch (readError) {
reject(readError);
}
});
resolve();
});
});
};
const inlineFileResources = (filePath, sourcePrefix) => {
const content = fs.readFileSync(filePath, 'utf8');
const inlineContents = inlineResourcesFromString(content, sourcePrefix, (url) =>
path.join(path.dirname(filePath), url)
);
fs.writeFileSync(filePath, inlineContents);
};
const inlineResourcesFromString = (content, sourcePrefix, callback) => [
inlineTemplate, inlineStyle, removeModuleId
].reduce((final, method) => method(final, sourcePrefix, callback), content);
const inlineTemplate = (content, sourcePrefix, callback) =>
content.replace(/templateUrl:\s*'([^']+?\.html)'/g, (match, url) => {
const mini = getMiniContents(url, sourcePrefix, callback);
return `template: "${mini}"`;
});
const inlineStyle = (content, sourcePrefix, callback) =>
content.replace(/styleUrls:\s*(\[[\s\S]*?\])/gm, (match, styleUrls) => {
const urls = eval(styleUrls); // string -> array
return 'styles: [' + urls.map((url) => {
const mini = getMiniContents(url, sourcePrefix, callback);
return `"${mini}"`;
}).join(',\n') + ']';
});
const getMiniContents = (url, sourcePrefix, callback) => {
const srcFile = callback(url);
const file = srcFile.replace(/^dist/, sourcePrefix)
const srcDir = file.slice(0, file.lastIndexOf(path.sep));
let template = '';
if (file.match(/\.s(a|c)ss$/)) {
// convert SASS -> CSS
template = sass.renderSync({
file,
importer: (url) => handleSassImport(url, srcDir)
});
template = template.css.toString();
} else {
template = fs.readFileSync(file, 'utf8');
}
return minifyText(template);
};
const handleSassImport = (url, srcDir) => {
const fullUrl = getFullSassUrl(url, srcDir);
let isPartial = false;
let validUrls = getSassUrls(fullUrl);
// if we can't find the file, try to
// see find it as a partial (underscore-prefixed)
if (validUrls.length === 0) {
validUrls = getSassUrls(fullUrl, true);
isPartial = true;
}
const file = getSassImportUrl(validUrls);
// CSS files don't get compiled in
return /\.css$/.test(file) ?
{ contents: fs.readFileSync(file, 'utf8') } :
{ file };
};
const getSassUrls = (url, partial) => {
let extensions = ['sass', 'scss'];
if (!partial) {
extensions = extensions.concat('', 'css');
} else {
const lastSlash = url.lastIndexOf(path.sep);
const urlDir = url.slice(0, lastSlash);
const fileName = url.slice(lastSlash + 1);
if (fileName[0] !== '_') {
url = urlDir + path.sep + '_' + fileName;
}
}
return extensions.reduce((valid, extension) => {
const extensionUrl = verifyUrl(url, extension);
if (extensionUrl) {
valid = valid.concat(extensionUrl);
}
return valid;
}, []);
};
const verifyUrl = (url, extension) => {
if (extension) {
url = url + `.${ extension }`;
}
if (!fs.existsSync(url)) {
url = null;
}
return url;
}
// convert ~-prefixed filenames to node_modules-prefixed
// make all others relative to srcDir
const getFullSassUrl = (url, srcDir) =>
/^~/.test(url) ?
path.resolve('node_modules', url.slice(1)) :
path.resolve(srcDir, url);
const getSassImportUrl = (validUrls) => {
if (validUrls.length !== 1) {
let error = 'Cannot determine Sass/CSS file to process. ';
if (validUrls.length === 0) {
error = error + `\n There are no files matching ${ url }`;
} else {
error = error + 'Candidates:\n ' + validUrls.join('\n ')
+ '\nPlease delete or rename all but one of these files or specify the extension to use.';
}
throw new Error(error);
}
return validUrls[0];
};
const minifyText = (text) => text
.replace(/([\n\r]\s*)+/gm, ' ')
.replace(/"/g, '\\"');
const removeModuleId = (content) =>
content.replace(/\s*moduleId:\s*module\.id\s*,?\s*/gm, '');
module.exports = inlineResources;
if (!module.parent) {
inlineResources('./build', 'src');
}

134
tasks/rollup.js Normal file
View file

@ -0,0 +1,134 @@
'use strict';
const erectorUtils = require('erector-set/src/utils');
const fs = require('fs-extra');
const librarianUtils = require('angular-librarian/commands/utilities');
const path = require('path');
const rollup = require('rollup');
const rollupCommon = require('rollup-plugin-commonjs');
const rollupNodeResolve = require('rollup-plugin-node-resolve');
const rollupSourcemaps = require('rollup-plugin-sourcemaps');
const rollupUglify = require('rollup-plugin-uglify');
const doRollup = (libName, dirs) => {
const nameParts = extractName(libName);
const es5Entry = path.resolve(dirs.es5, `${ nameParts.package }.js`);
const es2015Entry = path.resolve(dirs.es2015, `${ nameParts.package }.js`);
const destinations = generateDestinations(dirs.dist, nameParts);
const baseConfig = generateConfig({
entry: es5Entry,
external: [
'@angular/common',
'@angular/core'
],
globals: {
'@angular/common': 'ng.common',
'@angular/core': 'ng.core'
},
moduleName: librarianUtils.caseConvert.dashToCamel(nameParts.package),
onwarn: function rollupOnWarn(warning) {
// keeps TypeScript this errors down
if (warning.code !== 'THIS_IS_UNDEFINED') {
console.warn(warning.message);
}
},
plugins: [
rollupNodeResolve({
jsnext: true,
module: true
}),
rollupSourcemaps()
],
sourceMap: true
}, dirs.root);
const fesm2015Config = Object.assign({}, baseConfig, {
entry: es2015Entry,
dest: destinations.fesm2015,
format: 'es'
});
const fesm5Config = Object.assign({}, baseConfig, {
dest: destinations.fesm5,
format: 'es'
});
const minUmdConfig = Object.assign({}, baseConfig, {
dest: destinations.minUmd,
format: 'umd',
plugins: baseConfig.plugins.concat([rollupUglify({})])
});
const umdConfig = Object.assign({}, baseConfig, {
dest: destinations.umd,
format: 'umd'
});
const bundles = [
fesm2015Config,
fesm5Config,
minUmdConfig,
umdConfig
].map((config) =>
rollup.rollup(config).then((bundle) =>
bundle.write(config)
)
);
return Promise.all(bundles);
};
const extractName = (libName) => {
const isScoped = librarianUtils.checkIsScopedName(libName);
const nameParts = {
package: libName,
scope: undefined
};
if (isScoped) {
const parts = libName.split('/', 2);
nameParts.package = parts[1];
nameParts.scope = parts[0];
}
return nameParts;
};
const generateDestinations = (dist, nameParts) => {
const bundleDest = path.resolve(dist, 'bundles');
let fesmDest = path.resolve(dist);
if (nameParts.scope) {
fesmDest = path.resolve(fesmDest, nameParts.scope);
fs.ensureDirSync(fesmDest);
}
return Object.freeze({
fesm2015: path.resolve(fesmDest,`${ nameParts.package }.js`),
fesm5: path.resolve(fesmDest,`${ nameParts.package }.es5.js`),
minUmd: path.resolve(bundleDest, `${ nameParts.package }.umd.min.js`),
umd: path.resolve(bundleDest, `${ nameParts.package }.umd.js`)
});
};
const generateConfig = (base, rootDir) => {
let commonjsIncludes = ['node_modules/rxjs/**'];
const customLocation = path.resolve(rootDir, 'configs', 'rollup.config.js');
if (fs.existsSync(customLocation)) {
const custom = require(customLocation);
const external = (custom.external || []).filter((external) => base.external.indexOf(external) === -1);
const includes = (custom.commonjs || []).filter((include) => commonjsIncludes.indexOf(include) === -1);
base.external = base.external.concat(external);
base.globals = erectorUtils.mergeDeep(custom.globals, base.globals);
commonjsIncludes = commonjsIncludes.concat(includes);
}
base.plugins.unshift(
rollupCommon({
include: commonjsIncludes
})
);
return base;
};
module.exports = doRollup;

66
tasks/test.js Normal file
View file

@ -0,0 +1,66 @@
'use strict';
const fs = require('fs');
const path = require('path');
const Server = require('karma').Server;
function run(type) {
const config = getConfig(type);
const server = new Server(config, function(exitCode) {
process.exit(exitCode);
});
server.start();
}
function getConfig(type) {
switch (type) {
case 'headless':
case 'hl':
case 'h':
return getHeadlessConfig();
case 'all':
case 'a':
return getAllConfig();
case 'watch':
case 'w':
return getWatchConfig();
default:
return getSingleConfig();
}
}
function getSingleConfig() {
let config = getHeadlessConfig();
config.singleRun = true;
return config;
}
function getHeadlessConfig() {
let config = getAllConfig();
config.browsers = ['PhantomJS'];
return config;
}
function getWatchConfig() {
let config = getAllConfig(true);
config.browsers = ['Chrome'];
return config;
}
const getAllConfig = (watch) => ({
configFile: path.resolve(__dirname, '..', 'karma.conf.js'),
webpack: require(path.resolve(__dirname, '..', 'webpack', 'webpack.test.js'))(watch),
});
module.exports = run;
if (!module.parent) {
run(process.argv[2]);
}