Skip to content

Commit

Permalink
Support multiple entry inputs (#410)
Browse files Browse the repository at this point in the history
* support multiple entry inputs

* fix up tests

* add support for multi-entry cli builds

* upgrade to webpack-asset-relocator-loader 0.5.0

* loader update

* readme corrections, API to use files and separate symlinks object

* support array form

* deprecate use of filename

* fixup entry and shebang handling

* readme wording

* Update src/index.js

Co-Authored-By: Steven <steven@ceriously.com>
  • Loading branch information
guybedford and styfle committed May 28, 2019
1 parent bf98881 commit ddaf55a
Show file tree
Hide file tree
Showing 8 changed files with 248 additions and 214 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"@google-cloud/firestore": "^0.19.0",
"@sentry/node": "^4.3.0",
"@tensorflow/tfjs-node": "^0.3.0",
"@zeit/webpack-asset-relocator-loader": "0.5.0-beta.6",
"@zeit/webpack-asset-relocator-loader": "0.5.1",
"analytics-node": "^3.3.0",
"apollo-server-express": "^2.2.2",
"arg": "^4.1.0",
Expand Down
28 changes: 23 additions & 5 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ $ ncc build input.js -o dist

Outputs the Node.js compact build of `input.js` into `dist/index.js`.

It is also possible to build multiple files in a code-splitting build by passing additional inputs.

### Execution Testing

For testing and debugging, a file can be built into a temporary directory and executed with full source maps support with the command:
Expand Down Expand Up @@ -82,20 +84,36 @@ require('@zeit/ncc')('/path/to/input', {
v8cache: false, // default
quiet: false, // default
debugLog = false // default
}).then(({ code, map, assets }) => {
console.log(code);
// Assets is an object of asset file names to { source, permissions, symlinks }
// expected relative to the output code (if any)
}).then(({ files, symlinks }) => {
// files: an object of file names to { source, permissions }
// symlinks: an object of symlink mappings required for the build
// The main file is located at 'index.js':
files['index.js'].source
})
```

Multiple entry points can be built by providing an object to build. In this case, those files are avaiable as additional output files:

```js
require('@zeit/ncc')({
entry1: '/path/to/input1',
entry2: '/path/to/input2
}).then(({ files, symlinks }) => {
// named entry points are available at their names:
files['entry1.js'].source
files['entry1.js.map'].source
files['entry2.js'].source
// ... assets
});
```
When `watch: true` is set, the build object is not a promise, but has the following signature:
```js
{
// handler re-run on each build completion
// watch errors are reported on "err"
handler (({ err, code, map, assets }) => { ... })
handler (({ err, files, symlinks }) => { ... })
// handler re-run on each rebuild start
rebuild (() => {})
// close the watcher
Expand Down
94 changes: 46 additions & 48 deletions scripts/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,64 +6,62 @@ const copy = promisify(require("copy"));
const glob = promisify(require("glob"));
const bytes = require("bytes");

function assetList (files) {
return Object.keys(files).filter(file => file.startsWith('assets/')).map(file => file.substr(7));
}

async function main() {
for (const file of await glob(__dirname + "/../dist/**/*.@(js|cache|ts)")) {
unlinkSync(file);
}

const { code: cli, assets: cliAssets } = await ncc(
__dirname + "/../src/cli",
const { files: cliFiles } = await ncc(
{ 'cli': __dirname + "/../src/cli" },
{
filename: "cli.js",
externals: ["./index.js"],
minify: true,
v8cache: true
}
);
checkUnknownAssets('cli', Object.keys(cliAssets));
checkUnknownAssets('cli', assetList(cliFiles));

const { code: index, assets: indexAssets } = await ncc(
__dirname + "/../src/index",
const { files: indexFiles } = await ncc(
{ 'index': __dirname + "/../src/index" },
{
// we dont care about watching, so we don't want
// to bundle it. even if we did want watching and a bigger
// bundle, webpack (and therefore ncc) cannot currently bundle
// chokidar, which is quite convenient
externals: ["chokidar"],
filename: "index.js",
minify: true,
v8cache: true
}
);
checkUnknownAssets('index', Object.keys(indexAssets).filter(asset => !asset.startsWith('locales/') && asset !== 'worker.js' && asset !== 'index1.js'));
checkUnknownAssets('index', assetList(indexFiles).filter(asset => !asset.startsWith('locales/') && asset !== 'worker.js' && asset !== 'index.js'));

const { code: relocateLoader, assets: relocateLoaderAssets } = await ncc(
__dirname + "/../src/loaders/relocate-loader",
{ filename: "relocate-loader.js", minify: true, v8cache: true }
const { files: relocateLoaderFiles } = await ncc(
{ 'relocate-loader': __dirname + "/../src/loaders/relocate-loader",},
{ minify: true, v8cache: true }
);
checkUnknownAssets('relocate-loader', Object.keys(relocateLoaderAssets));
checkUnknownAssets('relocate-loader', assetList(relocateLoaderFiles));

const { code: shebangLoader, assets: shebangLoaderAssets } = await ncc(
__dirname + "/../src/loaders/shebang-loader",
{ filename: "shebang-loader.js", minify: true, v8cache: true }
const { files: shebangLoaderFiles } = await ncc(
{ 'shebang-loader': __dirname + "/../src/loaders/shebang-loader" },
{ minify: true, v8cache: true }
);
checkUnknownAssets('shebang-loader', Object.keys(shebangLoaderAssets));
checkUnknownAssets('shebang-loader', assetList(shebangLoaderFiles));

const { code: tsLoader, assets: tsLoaderAssets } = await ncc(
__dirname + "/../src/loaders/ts-loader",
{
filename: "ts-loader.js",
minify: true,
v8cache: true
}
const { files: tsLoaderFiles } = await ncc(
{ 'ts-loader': __dirname + "/../src/loaders/ts-loader" },
{ minify: true, v8cache: true }
);
checkUnknownAssets('ts-loader', Object.keys(tsLoaderAssets).filter(asset => !asset.startsWith('lib/') && !asset.startsWith('typescript/lib')));
checkUnknownAssets('ts-loader', assetList(tsLoaderFiles).filter(asset => !asset.startsWith('lib/') && !asset.startsWith('typescript/lib')));

const { code: sourcemapSupport, assets: sourcemapAssets } = await ncc(
require.resolve("source-map-support/register"),
{ filename: "sourcemap-register.js", minfiy: true, v8cache: true }
const { files: sourceMapSupportFiles } = await ncc(
{ 'sourcemap-register': require.resolve("source-map-support/register") },
{ minifiy: true, v8cache: true }
);
checkUnknownAssets('source-map-support/register', Object.keys(sourcemapAssets));
checkUnknownAssets('source-map-support/register', assetList(sourceMapSupportFiles));

// detect unexpected asset emissions from core build
function checkUnknownAssets (buildName, assets) {
Expand All @@ -73,22 +71,22 @@ async function main() {
console.log(assets);
}

writeFileSync(__dirname + "/../dist/ncc/cli.js.cache", cliAssets["cli.js.cache"].source);
writeFileSync(__dirname + "/../dist/ncc/index.js.cache", indexAssets["index.js.cache"].source);
writeFileSync(__dirname + "/../dist/ncc/sourcemap-register.js.cache", sourcemapAssets["sourcemap-register.js.cache"].source);
writeFileSync(__dirname + "/../dist/ncc/loaders/relocate-loader.js.cache", relocateLoaderAssets["relocate-loader.js.cache"].source);
writeFileSync(__dirname + "/../dist/ncc/loaders/shebang-loader.js.cache", shebangLoaderAssets["shebang-loader.js.cache"].source);
writeFileSync(__dirname + "/../dist/ncc/loaders/ts-loader.js.cache", tsLoaderAssets["ts-loader.js.cache"].source);

writeFileSync(__dirname + "/../dist/ncc/cli.js.cache.js", cliAssets["cli.js.cache.js"].source);
writeFileSync(__dirname + "/../dist/ncc/index.js.cache.js", indexAssets["index.js.cache.js"].source);
writeFileSync(__dirname + "/../dist/ncc/sourcemap-register.js.cache.js", sourcemapAssets["sourcemap-register.js.cache.js"].source);
writeFileSync(__dirname + "/../dist/ncc/loaders/relocate-loader.js.cache.js", relocateLoaderAssets["relocate-loader.js.cache.js"].source);
writeFileSync(__dirname + "/../dist/ncc/loaders/shebang-loader.js.cache.js", shebangLoaderAssets["shebang-loader.js.cache.js"].source);
writeFileSync(__dirname + "/../dist/ncc/loaders/ts-loader.js.cache.js", tsLoaderAssets["ts-loader.js.cache.js"].source);

writeFileSync(__dirname + "/../dist/ncc/cli.js", cli, { mode: 0o777 });
writeFileSync(__dirname + "/../dist/ncc/index.js", index);
writeFileSync(__dirname + "/../dist/ncc/cli.js.cache", cliFiles["cli.js.cache"].source);
writeFileSync(__dirname + "/../dist/ncc/index.js.cache", indexFiles["index.js.cache"].source);
writeFileSync(__dirname + "/../dist/ncc/sourcemap-register.js.cache", sourceMapSupportFiles["sourcemap-register.js.cache"].source);
writeFileSync(__dirname + "/../dist/ncc/loaders/relocate-loader.js.cache", relocateLoaderFiles["relocate-loader.js.cache"].source);
writeFileSync(__dirname + "/../dist/ncc/loaders/shebang-loader.js.cache", shebangLoaderFiles["shebang-loader.js.cache"].source);
writeFileSync(__dirname + "/../dist/ncc/loaders/ts-loader.js.cache", tsLoaderFiles["ts-loader.js.cache"].source);

writeFileSync(__dirname + "/../dist/ncc/cli.js.cache.js", cliFiles["cli.js.cache.js"].source);
writeFileSync(__dirname + "/../dist/ncc/index.js.cache.js", indexFiles["index.js.cache.js"].source);
writeFileSync(__dirname + "/../dist/ncc/sourcemap-register.js.cache.js", sourceMapSupportFiles["sourcemap-register.js.cache.js"].source);
writeFileSync(__dirname + "/../dist/ncc/loaders/relocate-loader.js.cache.js", relocateLoaderFiles["relocate-loader.js.cache.js"].source);
writeFileSync(__dirname + "/../dist/ncc/loaders/shebang-loader.js.cache.js", shebangLoaderFiles["shebang-loader.js.cache.js"].source);
writeFileSync(__dirname + "/../dist/ncc/loaders/ts-loader.js.cache.js", tsLoaderFiles["ts-loader.js.cache.js"].source);

writeFileSync(__dirname + "/../dist/ncc/cli.js", cliFiles["cli.js"].source, { mode: 0o777 });
writeFileSync(__dirname + "/../dist/ncc/index.js", indexFiles["index.js"].source);
writeFileSync(__dirname + "/../dist/ncc/typescript.js", `
const { Module } = require('module');
const m = new Module('', null);
Expand All @@ -104,10 +102,10 @@ catch (e) {
}
module.exports = typescript;
`);
writeFileSync(__dirname + "/../dist/ncc/sourcemap-register.js", sourcemapSupport);
writeFileSync(__dirname + "/../dist/ncc/loaders/relocate-loader.js", relocateLoader);
writeFileSync(__dirname + "/../dist/ncc/loaders/shebang-loader.js", shebangLoader);
writeFileSync(__dirname + "/../dist/ncc/loaders/ts-loader.js", tsLoader);
writeFileSync(__dirname + "/../dist/ncc/sourcemap-register.js", sourceMapSupportFiles["sourcemap-register.js"].source);
writeFileSync(__dirname + "/../dist/ncc/loaders/relocate-loader.js", relocateLoaderFiles["relocate-loader.js"].source);
writeFileSync(__dirname + "/../dist/ncc/loaders/shebang-loader.js", shebangLoaderFiles["shebang-loader.js"].source);
writeFileSync(__dirname + "/../dist/ncc/loaders/ts-loader.js", tsLoaderFiles["ts-loader.js"].source);
writeFileSync(__dirname + "/../dist/ncc/loaders/uncacheable.js", readFileSync(__dirname + "/../src/loaders/uncacheable.js"));
writeFileSync(__dirname + "/../dist/ncc/loaders/empty-loader.js", readFileSync(__dirname + "/../src/loaders/empty-loader.js"));
writeFileSync(__dirname + "/../dist/ncc/loaders/notfound-loader.js", readFileSync(__dirname + "/../src/loaders/notfound-loader.js"));
Expand Down
99 changes: 38 additions & 61 deletions src/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

const { resolve, relative, dirname, sep } = require("path");
const glob = require("glob");
const shebangRegEx = require("./utils/shebang");
const rimraf = require("rimraf");
const crypto = require("crypto");
const { writeFileSync, unlink, existsSync, symlinkSync } = require("fs");
Expand All @@ -12,8 +11,8 @@ const { version: nccVersion } = require('../package.json');
const usage = `Usage: ncc <cmd> <opts>
Commands:
build <input-file> [opts]
run <input-file> [opts]
build <input-file>+ [opts]
run <input-file> <args>? [opts]
cache clean|dir|size
help
version
Expand Down Expand Up @@ -49,58 +48,39 @@ else {
api = true;
}

function renderSummary(code, map, assets, outDir, buildTime) {
function renderSummary(files, outDir, buildTime) {
if (outDir && !outDir.endsWith(sep)) outDir += sep;
const codeSize = Math.round(Buffer.byteLength(code, "utf8") / 1024);
const mapSize = map ? Math.round(Buffer.byteLength(map, "utf8") / 1024) : 0;
const assetSizes = Object.create(null);
let totalSize = codeSize;
let maxAssetNameLength = 8 + (map ? 4 : 0); // length of index.js(.map)?
for (const asset of Object.keys(assets)) {
const assetSource = assets[asset].source;
const fileSizes = Object.create(null);
let totalSize = 0;
let maxAssetNameLength = 0;
for (const file of Object.keys(files)) {
const assetSource = files[file].source;
if (!assetSource) continue;
const assetSize = Math.round(
(assetSource.byteLength || Buffer.byteLength(assetSource, "utf8")) / 1024
);
assetSizes[asset] = assetSize;
fileSizes[file] = assetSize;
totalSize += assetSize;
if (asset.length > maxAssetNameLength) maxAssetNameLength = asset.length;
if (file.length > maxAssetNameLength) maxAssetNameLength = file.length;
}
const orderedAssets = Object.keys(assets).sort((a, b) =>
assetSizes[a] > assetSizes[b] ? 1 : -1
);
const orderedAssets = Object.keys(files).filter(file => typeof files[file] === 'object').sort((a, b) => {
if ((a.startsWith('asset/') || b.startsWith('asset/')) &&
!(a.startsWith('asset/') && b.startsWith('asset/')))
return a.startsWith('asset/') ? 1 : -1;
return fileSizes[a] > fileSizes[b] ? 1 : -1;
});

const sizePadding = totalSize.toString().length;

let indexRender = `${codeSize
.toString()
.padStart(sizePadding, " ")}kB ${outDir}${"index.js"}`;
let indexMapRender = map ? `${mapSize
.toString()
.padStart(sizePadding, " ")}kB ${outDir}${"index.js.map"}` : '';

let output = "",
first = true;
for (const asset of orderedAssets) {
for (const file of orderedAssets) {
if (first) first = false;
else output += "\n";
if (codeSize < assetSizes[asset] && indexRender) {
output += indexRender + "\n";
indexRender = null;
}
if (mapSize && mapSize < assetSizes[asset] && indexMapRender) {
output += indexMapRender + "\n";
indexMapRender = null;
}
output += `${assetSizes[asset]
output += `${fileSizes[file]
.toString()
.padStart(sizePadding, " ")}kB ${outDir}${asset}`;
}

if (indexRender) {
output += (first ? "" : "\n") + indexRender;
first = false;
.padStart(sizePadding, " ")}kB ${outDir}${file}`;
}
if (indexMapRender) output += (first ? "" : "\n") + indexMapRender;

output += `\n${totalSize}kB [${buildTime}ms] - ncc ${nccVersion}`;

Expand Down Expand Up @@ -187,8 +167,7 @@ async function runCmd (argv, stdout, stderr) {

break;
case "run":
if (args._.length > 2)
errTooManyArguments("run");
var runArgs = args._.slice(2);

if (args["--out"])
errFlagNotCompatible("--out", "run");
Expand All @@ -206,14 +185,12 @@ async function runCmd (argv, stdout, stderr) {

// fallthrough
case "build":
if (args._.length > 2)
errTooManyArguments("build");
const buildFiles = runArgs ? resolve(args._[1]) : args._.slice(1);

let startTime = Date.now();
let ps;
const buildFile = eval("require.resolve")(resolve(args._[1] || "."));
const ncc = require("./index.js")(
buildFile,
buildFiles,
{
debugLog: args["--debug"],
minify: args["--minify"],
Expand All @@ -227,7 +204,7 @@ async function runCmd (argv, stdout, stderr) {
}
);

async function handler ({ err, code, map, assets, symlinks }) {
async function handler ({ err, files, symlinks }) {
// handle watch errors
if (err) {
stderr.write(err + '\n');
Expand All @@ -245,26 +222,25 @@ async function runCmd (argv, stdout, stderr) {
new Promise((resolve, reject) => unlink(file, err => err ? reject(err) : resolve())
))
);
writeFileSync(outDir + "/index.js", code, { mode: code.match(shebangRegEx) ? 0o777 : 0o666 });
if (map) writeFileSync(outDir + "/index.js.map", map);

for (const asset of Object.keys(assets)) {
const assetPath = outDir + "/" + asset;
mkdirp.sync(dirname(assetPath));
writeFileSync(assetPath, assets[asset].source, { mode: assets[asset].permissions });
for (const filename of Object.keys(files)) {
const file = files[filename];
const filePath = outDir + "/" + filename;
mkdirp.sync(dirname(filePath));
writeFileSync(filePath, file.source, { mode: file.permissions });
}

for (const symlink of Object.keys(symlinks)) {
const symlinkPath = outDir + "/" + symlink;
symlinkSync(symlinks[symlink], symlinkPath);
for (const filename of Object.keys(symlinks)) {
const file = symlinks[filename];
const filePath = outDir + "/" + filename;
mkdirp.sync(dirname(filePath));
symlinkSync(file, filePath);
}

if (!quiet) {
stdout.write(
stdout.write(
renderSummary(
code,
map,
assets,
files,
run ? "" : relative(process.cwd(), outDir),
Date.now() - startTime,
) + '\n'
Expand All @@ -277,6 +253,7 @@ async function runCmd (argv, stdout, stderr) {
if (run) {
// find node_modules
const root = resolve('/node_modules');
const buildFile = resolve(args._[1]);
let nodeModulesDir = dirname(buildFile) + "/node_modules";
do {
if (nodeModulesDir === root) {
Expand All @@ -288,7 +265,7 @@ async function runCmd (argv, stdout, stderr) {
} while (nodeModulesDir = resolve(nodeModulesDir, "../../node_modules"));
if (nodeModulesDir)
symlinkSync(nodeModulesDir, outDir + "/node_modules", "junction");
ps = require("child_process").fork(outDir + "/index.js", {
ps = require("child_process").fork(outDir + "/index.js", runArgs, {
stdio: api ? 'pipe' : 'inherit'
});
if (api) {
Expand Down
Loading

0 comments on commit ddaf55a

Please sign in to comment.