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

Support multiple entry inputs #410

Merged
merged 11 commits into from
May 28, 2019
Merged
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 }) => {
guybedford marked this conversation as resolved.
Show resolved Hide resolved
// files: an object of file names to { source, permissions }
// symlinks: an object of symlink mappings required for the build
styfle marked this conversation as resolved.
Show resolved Hide resolved
// 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')({
styfle marked this conversation as resolved.
Show resolved Hide resolved
entry1: '/path/to/input1',
entry2: '/path/to/input2
}).then(({ files, symlinks }) => {
// named entry points are available at their names:
files['entry1.js'].source
styfle marked this conversation as resolved.
Show resolved Hide resolved
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