Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor build -- 13x faster typescript bundling #1558

Merged
merged 13 commits into from
Apr 14, 2021
Merged
25 changes: 7 additions & 18 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,28 +33,22 @@
]
},
"scripts": {
"clean": "lerna exec -- rimraf dist .rpt2_cache_development .rpt2_cache_production package-lock.json",
"clean": "lerna exec -- rimraf dist tmpDist .rpt2_cache_development .rpt2_cache_production package-lock.json",
"circularDeps": "lerna exec -- madge --circular src/index.ts",
"coveralls": "coveralls < coverage/lcov.info || true",
"debug": "node --inspect-brk ./node_modules/jest/bin/jest --runInBand --no-cache --no-watchman",
"docs-build": "cross-env NODE_ENV=production node docs/build.js",
"docs-build:dev": "node docs/build.js",
"prebuild": "npm run clean",
"postbuild": "rimraf build",
"build": "run-s build:s:** && run-p build:p:** && npm run print:size && npm run move-types",
"build:s:tsc": "lerna exec tsc -- --noEmit",
"build:s:es": "npm run rollup -- --name=index --ext=.esm.js --env=production --format=es --minify=false",
"build:s:es:dev": "npm run rollup -- --name=index --ext=.dev.esm.js --env=development --format=es --minify=false",
"build:p:esnext": "npm run rollup -- --name=index --ext=.esnext.js --env=production --format=es --esnext=true --minify=false",
"build:p:cjs": "npm run rollup -- --env=development --format=cjs --replace=true --name=index.cjs --minify=false",
"build:p:cjs:prod": "npm run rollup -- --env=production --format=cjs --replace=true --name=index.cjs --minify=true --ext=.min.js",
"build:p:umd:dev": "npm run rollup -- --minify=false",
"build:p:umd:prod": "npm run rollup -- --env=production --ext=.min.js",
"postbuild": "rimraf build; lerna exec -- rimraf tmpDist",
"build": "npm run clean && tsc --build && npm run build:move-compiled && npm run build:move-types && npm run build:bundle && npm run print:size",
"build:bundle": "lerna exec --stream -- node ../../scripts/rollup/build.js",
"build:move-types": "rimraf build/**/*.js && lerna exec -- node ../../scripts/bundle/move-typedefs.js",
"build:move-compiled": "lerna exec -- node ../../scripts/bundle/move-compiled.js",
"print:size": "node scripts/bundle/bundle-size.js",
"install:browsertest": "cd fixtures/browser && npm install",
"lint": "tslint -p tsconfig.json --project",
"lint-staged": "lint-staged",
"move-types": "lerna exec -- node ../../scripts/bundle/move-typings.js",
"publish": "lerna publish --exact --force-publish=*",
"version": "npm run build && npm run docs-build && git add docs && npm run test",
"prettier": "npm-run-all prettier:*",
Expand All @@ -64,7 +58,6 @@
"prettier:fixtures": "prettier --print-width 160 --trailing-comma none --single-quote true --write --list-different fixtures/**/*.{js,jsx}",
"prettier:scripts": "prettier --print-width 160 --trailing-comma none --single-quote true --write --list-different scripts/**/*.{js,jsx}",
"postinstall": "lerna bootstrap --no-ci && run-p install:* && npm run build",
"rollup": "lerna exec -- node ../../scripts/rollup/build.js",
"test": "npm run test:node && npm run test:browser",
"test:node": "cross-env NODE_ENV=test jest --no-watchman",
"test:coverage": "cross-env NODE_ENV=test jest --runInBand --coverage --no-watchman",
Expand All @@ -75,11 +68,7 @@
"test:browser:debug-nocompat": "cross-env InfernoCompat=0 npm run --prefix fixtures/browser debug",
"test:browser:sauce": "npm run --prefix fixtures/browser test",
"test:react": "npm run --prefix fixtures/react test -- --forceExit",
"test:package": "node fixtures/packaging/build-all.js",
"quick-test:browser": "run-s build:s:** && npm run build:p:cjs:prod && npm run test:browser:nocompat",
"quick-test:browser:debug": "run-s build:s:** && npm run build:p:cjs:prod && npm run test:browser:debug-nocompat",
"quick-test:browser-inferno": "lerna exec --scope 'inferno' tsc -- --noEmit && lerna exec --scope 'inferno' -- node ../../scripts/rollup/build.js --name=index --ext=.dev.esm.js --env=development --format=es --minify=false && lerna exec --scope 'inferno' -- node ../../scripts/rollup/build.js --env=production --format=cjs --replace=true --name=index.cjs --minify=true --ext=.min.js && npm run test:browser:nocompat",
"quick-test:browser-inferno:debug": "lerna exec --scope 'inferno' tsc -- --noEmit && lerna exec --scope 'inferno' -- node ../../scripts/rollup/build.js --name=index --ext=.dev.esm.js --env=development --format=es --minify=false && lerna exec --scope 'inferno' -- node ../../scripts/rollup/build.js --env=production --format=cjs --replace=true --name=index.cjs --minify=true --ext=.min.js && npm run test:browser:debug-nocompat"
"test:package": "node fixtures/packaging/build-all.js"
},
"devDependencies": {
"@babel/core": "^7.13.14",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
const rimraf = require('rimraf');
const join = require('path').join;
const cwd = process.cwd();
const pkgJSON = require(join(cwd, 'package.json'));
Expand All @@ -8,5 +7,4 @@ if (pkgJSON.private) {
return;
}
// Move type declaration files generated by TSC to dist folder
mergedirs(join(cwd, 'dist/packages/', pkgJSON.name, 'src'), join(cwd, 'dist'), 'overwrite');
rimraf.sync(join(cwd, 'dist/packages/'));
mergedirs(join(cwd, '../../build/packages/', pkgJSON.name, 'src'), join(cwd, 'tmpDist'), 'overwrite');
10 changes: 10 additions & 0 deletions scripts/bundle/move-typedefs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const join = require('path').join;
const cwd = process.cwd();
const pkgJSON = require(join(cwd, 'package.json'));
const mergedirs = require('merge-dirs').default;

if (pkgJSON.private) {
return;
}
// Move type declaration files generated by TSC to dist folder
mergedirs(join(cwd, '../../build/packages/', pkgJSON.name, 'src'), join(cwd, 'dist'), 'overwrite');
157 changes: 124 additions & 33 deletions scripts/rollup/build.js
Original file line number Diff line number Diff line change
@@ -1,47 +1,97 @@
const { mkdir } = require('fs');
const { promises: { mkdir: mkdirAsync }, lstatSync, readdirSync } = require('fs');
const { join } = require('path');
const createPlugins = require('./plugins');
const { rollup } = require('rollup');


const cwd = process.cwd();
const ROOT = join(__dirname, '../../packages');

const pkgJSON = require(join(cwd, 'package.json'));

if (pkgJSON.private || !pkgJSON.rollup) {
return;
}

mkdir(join(cwd, 'dist'), (err) => {
if (err && err.code !== 'EEXIST') {
throw Error(e);
}

var exit = process.exit;

const options = require('minimist')(process.argv.slice(2), {
boolean: ['replace', 'optimize', 'minify'],
default: {
env: 'development',
ext: '.js',
format: 'umd',
name: pkgJSON.name,
replace: true,
minify: true,
version: pkgJSON.version
const args = require('minimist')(process.argv.slice(2));

const moduleGlobals = readdirSync(ROOT)
.filter((path) => lstatSync(join(ROOT, path)).isDirectory())
.reduce((acc, pkgName) => {
const pkgJSON = require(join(ROOT, pkgName, 'package.json'));

if (pkgJSON.rollup && pkgJSON.rollup.moduleName) {
acc[pkgJSON.name] = pkgJSON.rollup.moduleName;
}
});

const createRollup = require('./rollup');
const createBundle = require('./bundle');
return acc;
}, {});

const rollup = createRollup(options);
const bundle = createBundle(options);
// Self calling function to allow async/await for readability
(async () => {

rollup
.then(bundle)
.then(() => {
console.log(`${pkgJSON.name} in ${options.format} is DONE`);
})
.catch((error) => {
// Create dist folder
await mkdirAsync(join(cwd, 'dist')).catch(err => {
if (err.code !== 'EEXIST') {
throw Error(err);
}
});

// Get info from package.json
const {
version,
rollup: rollupConfig = {},
dependencies = {}, devDependencies = {}, peerDependencies = {}
} = pkgJSON;

// Figure out from package.json what dependencies to bundle
function exclusionFilter(name) {
return !(rollupConfig.bundledDependencies || []).includes(name);
}
const deps = Object.assign({}, devDependencies, peerDependencies, dependencies);
const external = Object.keys(deps)
.filter(exclusionFilter)
.filter(function (elem, index, self) {
return index === self.indexOf(elem);
});

// The import stream is NodeJS specific and should alwats be marked as external
// Used in inferno-server
external.push('stream');

const defaultOptions = {
name: 'index',
replace: true,
version: pkgJSON.version
}

const targets = [
//esmDev --name=index --ext=.dev.esm.js --env=development --format=es --minify=false
Object.assign({}, defaultOptions, { env: 'development', format: 'es', minify: false, ext: '.dev.esm.js'}),
//esmProd --name=index --ext=.esm.js --env=production --format=es --minify=false
Object.assign({}, defaultOptions, { env: 'production', format: 'es', minify: false, ext: '.esm.js'}),
//esNext --name=index --ext=.esnext.js --env=production --format=es --esnext=true --minify=false
Object.assign({}, defaultOptions, { env: 'production', format: 'es', esnext: true, minify: false, ext: '.esnext.js'}),
//cjsDev --env=development --format=cjs --replace=true --name=index.cjs --minify=false
Object.assign({}, defaultOptions, { env: 'development', format: 'cjs', minify: false, ext: '.cjs.js'}),
//cjsProd --env=production --format=cjs --replace=true --name=index.cjs --minify=true --ext=.min.js
Object.assign({}, defaultOptions, { env: 'production', format: 'cjs', minify: true, ext: '.cjs.min.js'}),
//umdDev --minify=false
Object.assign({}, defaultOptions, { env: 'development', format: 'umd', minify: false, name: pkgJSON.name, ext: '.js'}),
//umdProd --env=production --ext=.min.js
Object.assign({}, defaultOptions, { env: 'production', format: 'umd', minify: true, name: pkgJSON.name, ext: '.min.js'}),
].filter((target) => {
if (args['target-ext']) {
return target.ext === args['target-ext'];
}
return true
})

await targets.forEach(async (options) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if the alias plugin is needed anymore? Also would it be possible to build all packages using rollup inputs rather than spawning new process for each package using lerna?

Copy link
Contributor Author

@jhsware jhsware Apr 13, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting! I didn't investigate the alias plugin due to lack of time. I ran into an issue that I now realise might have been related to that plugin but it solved itself. If we can remove it we should because it makes the build process harder to understand.

I am not sure that I understand what you mean by "using rollup inputs". I chose to let lerna exec take care of parallel execution because I wanted to simplify everything to allow a lower barrier of entry for further optimisation. If esbuild solves const enums I believe the esm and esmnext parts of the transforms could be done using esbuild without making the build process more complex, that would cut build time to 2.5mins.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alias.js appears to be important to the build workflow. It allows packages to use transpiled output during bundling.

const errorFunc = (error) => {
console.error(error); // Print whole error object

if (error.snippet) {
console.error('\u001b[31;1m');
console.error('\n-------- Details -------');
Expand All @@ -52,8 +102,49 @@ mkdir(join(cwd, 'dist'), (err) => {
console.error('\n-------------------------');
console.error('\u001b[0m');
}

console.error(`${pkgJSON.name} in ${options.format} is FAILED ${error.message}`);
exit(-1); // Do not continue build in case of error to avoid publishing garbage, Github #1157
});
});
process.exit(-1); // Do not continue build in case of error to avoid publishing garbage, Github #1157
};

// Minify settings are found in plugins/index.js
const rollupPlugins = createPlugins(version, options);

// Transform
const { write } = await rollup({
input: join(cwd, 'tmpDist/index.js'),
external: external,
plugins: rollupPlugins
}).catch(errorFunc);

// Write bundle
const filename = `${options.name}${options.ext}`;

// TODO: Consider adding globals to avoid warnings:
// No name was provided for external module 'stream' in output.globals – guessing 'stream'
// No name was provided for external module 'history' in output.globals – guessing 'history'
// No name was provided for external module 'path-to-regexp-es6' in output.globals – guessing 'pathToRegexp'
// No name was provided for external module 'hoist-non-inferno-statics' in output.globals – guessing 'hoistNonReactStatics'
// No name was provided for external module 'redux' in output.globals – guessing 'redux'
// No name was provided for external module 'mobx' in output.globals – guessing 'mobx'

const bundleOptions = {
output: {
file: `dist/${filename}`,
format: options.format,
globals: Object.assign(moduleGlobals, rollupConfig.moduleGlobals),
name: rollupConfig.moduleName,
indent: true,
extend: true,
sourcemap: false
}
};

if (options.format === 'cjs') {
bundleOptions.output.exports = 'named';
}

await write(bundleOptions).catch(errorFunc);
console.log(`${pkgJSON.name} in ${options.name}${options.ext} is DONE`);
});
})();
41 changes: 0 additions & 41 deletions scripts/rollup/bundle.js

This file was deleted.

26 changes: 13 additions & 13 deletions scripts/rollup/plugins/alias.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,18 @@ const ROOT = join(__dirname, '../../../');
module.exports = alias({
resolve: ['.js'],
entries: [
{ find: 'inferno', replacement: resolve(ROOT, 'packages/inferno/dist/index.esm.js') },
{ find: 'inferno-animation', replacement: resolve(ROOT, 'packages/inferno-animation/dist/index.esm.js') },
{ find: 'inferno-compat', replacement: resolve(ROOT, 'packages/inferno-compat/dist/index.esm.js') },
{ find: 'inferno-create-class', replacement: resolve(ROOT, 'packages/inferno-create-class/dist/index.esm.js') },
{ find: 'inferno-create-element', replacement: resolve(ROOT, 'packages/inferno-create-element/dist/index.esm.js') },
{ find: 'inferno-hyperscript', replacement: resolve(ROOT, 'packages/inferno-hyperscript/dist/index.esm.js') },
{ find: 'inferno-mobx', replacement: resolve(ROOT, 'packages/inferno-mobx/dist/index.esm.js') },
{ find: 'inferno-redux', replacement: resolve(ROOT, 'packages/inferno-redux/dist/index.esm.js') },
{ find: 'inferno-router', replacement: resolve(ROOT, 'packages/inferno-router/dist/index.esm.js') },
{ find: 'inferno-server', replacement: resolve(ROOT, 'packages/inferno-server/dist/index.esm.js') },
{ find: 'inferno-shared', replacement: resolve(ROOT, 'packages/inferno-shared/dist/index.esm.js') },
{ find: 'inferno-clone-vnode', replacement: resolve(ROOT, 'packages/inferno-clone-vnode/dist/index.esm.js') },
{ find: 'inferno-hydrate', replacement: resolve(ROOT, 'packages/inferno-hydrate/dist/index.esm.js') }
{ find: 'inferno', replacement: resolve(ROOT, 'packages/inferno/tmpDist/index.js') },
{ find: 'inferno-animation', replacement: resolve(ROOT, 'packages/inferno-animation/tmpDist/index.js') },
{ find: 'inferno-compat', replacement: resolve(ROOT, 'packages/inferno-compat/tmpDist/index.js') },
{ find: 'inferno-create-class', replacement: resolve(ROOT, 'packages/inferno-create-class/tmpDist/index.js') },
{ find: 'inferno-create-element', replacement: resolve(ROOT, 'packages/inferno-create-element/tmpDist/index.js') },
{ find: 'inferno-hyperscript', replacement: resolve(ROOT, 'packages/inferno-hyperscript/tmpDist/index.js') },
{ find: 'inferno-mobx', replacement: resolve(ROOT, 'packages/inferno-mobx/tmpDist/index.js') },
{ find: 'inferno-redux', replacement: resolve(ROOT, 'packages/inferno-redux/tmpDist/index.js') },
{ find: 'inferno-router', replacement: resolve(ROOT, 'packages/inferno-router/tmpDist/index.js') },
{ find: 'inferno-server', replacement: resolve(ROOT, 'packages/inferno-server/tmpDist/index.js') },
{ find: 'inferno-shared', replacement: resolve(ROOT, 'packages/inferno-shared/tmpDist/index.js') },
{ find: 'inferno-clone-vnode', replacement: resolve(ROOT, 'packages/inferno-clone-vnode/tmpDist/index.js') },
{ find: 'inferno-hydrate', replacement: resolve(ROOT, 'packages/inferno-hydrate/tmpDist/index.js') }
]
});
10 changes: 1 addition & 9 deletions scripts/rollup/plugins/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,19 @@ const bublePlugin = require('rollup-plugin-buble');
const commonjs = require('rollup-plugin-commonjs');
const nodeResolve = require('rollup-plugin-node-resolve');
const replacePlugin = require('rollup-plugin-replace');
const tsPlugin = require('rollup-plugin-typescript2');
const terser = require('rollup-plugin-terser').terser;
const aliasPlugin = require('./alias');

module.exports = function (version, options) {
const plugins = [
aliasPlugin,
nodeResolve({
extensions: ['.ts', '.js', '.json'],
extensions: ['.js', '.json'],
mainFields: ['module', 'main'],
preferBuiltins: true
}),
commonjs({
include: 'node_modules/**'
}),
tsPlugin({
abortOnError: true,
cacheRoot: `.rpt2_cache_${options.env}`,
check: false,
clean: true,
tsconfig: __dirname + '/../../../tsconfig.json' // Have absolute path to fix windows build
})
];

Expand Down
32 changes: 0 additions & 32 deletions scripts/rollup/rollup.js

This file was deleted.