Skip to content

Commit

Permalink
Add support for .cjs outputs (#511)
Browse files Browse the repository at this point in the history
This PR adds support for `.cjs` output files from ncc, when the input file is using the `.cjs` extension.

As included in the readme comment, this is useful for packages setting `"type": "module"` in the package.json that want '.js' files in the package to be able to be modules, and thus need the '.cjs' extension on all CommonJS files.

Co-authored-by: Steven <[email protected]>
  • Loading branch information
guybedford and styfle authored Feb 21, 2020
1 parent e3e34b5 commit 1445ffc
Show file tree
Hide file tree
Showing 6 changed files with 47 additions and 26 deletions.
3 changes: 3 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ $ ncc build input.js -o dist

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

> Note: If the input file is using a `.cjs` extension, then so will the corresponding output file.
> This is useful for packages that want to use `.js` files as modules in native Node.js using
> a `"type": "module"` in the package.json file.
#### Commands:
```
Expand Down
20 changes: 11 additions & 9 deletions src/cli.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env node

const { resolve, relative, dirname, sep } = require("path");
const { resolve, relative, dirname, sep, extname } = require("path");
const glob = require("glob");
const shebangRegEx = require("./utils/shebang");
const rimraf = require("rimraf");
Expand Down Expand Up @@ -50,7 +50,7 @@ else {
api = true;
}

function renderSummary(code, map, assets, outDir, buildTime) {
function renderSummary(code, map, assets, ext, 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;
Expand All @@ -74,10 +74,10 @@ function renderSummary(code, map, assets, outDir, buildTime) {

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

let output = "",
first = true;
Expand Down Expand Up @@ -215,6 +215,7 @@ async function runCmd (argv, stdout, stderr) {
let startTime = Date.now();
let ps;
const buildFile = eval("require.resolve")(resolve(args._[1] || "."));
const ext = buildFile.endsWith('.cjs') ? '.cjs' : '.js';
const ncc = require("./index.js")(
buildFile,
{
Expand All @@ -241,16 +242,16 @@ async function runCmd (argv, stdout, stderr) {

outDir = outDir || resolve("dist");
mkdirp.sync(outDir);
// remove all existing ".js" files in the out directory
// remove all existing ".js" and ".cjs" files in the out directory
await Promise.all(
(await new Promise((resolve, reject) =>
glob(outDir + '/**/*.js', (err, files) => err ? reject(err) : resolve(files))
glob(outDir + '/**/*.(js|cjs)', (err, files) => err ? reject(err) : resolve(files))
)).map(file =>
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);
writeFileSync(`${outDir}/index${ext}`, code, { mode: code.match(shebangRegEx) ? 0o777 : 0o666 });
if (map) writeFileSync(`${outDir}/index${ext}.map`, map);

for (const asset of Object.keys(assets)) {
const assetPath = outDir + "/" + asset;
Expand All @@ -269,6 +270,7 @@ async function runCmd (argv, stdout, stderr) {
code,
map,
assets,
ext,
run ? "" : relative(process.cwd(), outDir),
Date.now() - startTime,
) + '\n'
Expand All @@ -292,7 +294,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${ext}`, {
stdio: api ? 'pipe' : 'inherit'
});
if (api) {
Expand Down
36 changes: 19 additions & 17 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,19 @@
const resolve = require("resolve");
const fs = require("graceful-fs");
const crypto = require("crypto");
const { sep, join, dirname } = require("path");
const { join, dirname, extname } = require("path");
const webpack = require("webpack");
const MemoryFS = require("memory-fs");
const terser = require("terser");
const tsconfigPaths = require("tsconfig-paths");
const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin");
const shebangRegEx = require('./utils/shebang');
const { pkgNameRegEx } = require("./utils/get-package-base");
const nccCacheDir = require("./utils/ncc-cache-dir");
const { version: nccVersion } = require('../package.json');

// support glob graceful-fs
fs.gracefulify(require("fs"));

const nodeBuiltins = new Set([...require("repl")._builtinLibs, "constants", "module", "timers", "console", "_stream_writable", "_stream_readable", "_stream_duplex"]);

const SUPPORTED_EXTENSIONS = [".js", ".json", ".node", ".mjs", ".ts", ".tsx"];

const hashOf = name => {
Expand All @@ -36,7 +33,7 @@ module.exports = (
{
cache,
externals = [],
filename = "index.js",
filename = 'index' + (entry.endsWith('.cjs') ? '.cjs' : '.js'),
minify = false,
sourceMap = false,
sourceMapRegister = true,
Expand All @@ -49,6 +46,8 @@ module.exports = (
transpileOnly = false
} = {}
) => {
const ext = extname(filename);

if (!quiet) {
console.log(`ncc: Version ${nccVersion}`);
console.log(`ncc: Compiling file ${filename}`);
Expand All @@ -61,11 +60,11 @@ module.exports = (
const existingAssetNames = [filename];
if (sourceMap) {
existingAssetNames.push(`${filename}.map`);
existingAssetNames.push('sourcemap-register.js');
existingAssetNames.push(`sourcemap-register${ext}`);
}
if (v8cache) {
existingAssetNames.push(`${filename}.cache`);
existingAssetNames.push(`${filename}.cache.js`);
existingAssetNames.push(`${filename}.cache${ext}`);
}
const resolvePlugins = [];
// add TsconfigPathsPlugin to support `paths` resolution in tsconfig
Expand Down Expand Up @@ -140,7 +139,8 @@ module.exports = (
target: "node",
output: {
path: "/",
filename,
// Webpack only emits sourcemaps for files ending in .js
filename: ext === '.cjs' ? filename + '.js' : filename,
libraryTarget: "commonjs2"
},
resolve: {
Expand Down Expand Up @@ -341,10 +341,12 @@ module.exports = (
if (resolved in assets)
symlinks[key] = value;
}
delete assets[filename];
delete assets[`${filename}.map`];
let code = mfs.readFileSync(`/${filename}`, "utf8");
let map = sourceMap ? mfs.readFileSync(`/${filename}.map`, "utf8") : null;
// Webpack only emits sourcemaps for .js files
// so we need to adjust the .cjs extension handling
delete assets[filename + (ext === '.cjs' ? '.js' : '')];
delete assets[`${filename}${ext === '.cjs' ? '.js' : ''}.map`];
let code = mfs.readFileSync(`/${filename}${ext === '.cjs' ? '.js' : ''}`, "utf8");
let map = sourceMap ? mfs.readFileSync(`/${filename}${ext === '.cjs' ? '.js' : ''}.map`, "utf8") : null;

if (map) {
map = JSON.parse(map);
Expand Down Expand Up @@ -385,24 +387,24 @@ module.exports = (

if (v8cache) {
const { Script } = require('vm');
assets[filename + '.cache'] = { source: new Script(code).createCachedData(), permissions: defaultPermissions };
assets[filename + '.cache.js'] = { source: code, permissions: defaultPermissions };
assets[`${filename}.cache`] = { source: new Script(code).createCachedData(), permissions: defaultPermissions };
assets[`${filename}.cache${ext}`] = { source: code, permissions: defaultPermissions };
if (map) {
assets[filename + '.map'] = { source: JSON.stringify(map), permissions: defaultPermissions };
map = undefined;
}
code =
`const { readFileSync, writeFileSync } = require('fs'), { Script } = require('vm'), { wrap } = require('module');\n` +
`const source = readFileSync(__dirname + '/${filename}.cache.js', 'utf-8');\n` +
`const source = readFileSync(__dirname + '/${filename}.cache${ext}', 'utf-8');\n` +
`const cachedData = !process.pkg && require('process').platform !== 'win32' && readFileSync(__dirname + '/${filename}.cache');\n` +
`const script = new Script(wrap(source), cachedData ? { cachedData } : {});\n` +
`(script.runInThisContext())(exports, require, module, __filename, __dirname);\n` +
`if (cachedData) process.on('exit', () => { try { writeFileSync(__dirname + '/${filename}.cache', script.createCachedData()); } catch(e) {} });\n`;
}

if (sourceMap && sourceMapRegister) {
code = `require('./sourcemap-register.js');` + code;
assets['sourcemap-register.js'] = { source: fs.readFileSync(__dirname + "/sourcemap-register.js.cache.js"), permissions: defaultPermissions };
code = `require('./sourcemap-register${ext}');` + code;
assets[`sourcemap-register${ext}`] = { source: fs.readFileSync(`${__dirname}/sourcemap-register.js.cache.js`), permissions: defaultPermissions };
}

if (shebangMatch) {
Expand Down
12 changes: 12 additions & 0 deletions test/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,17 @@
{
args: ["run", "-t", "test/fixtures/with-type-errors/ts-error.ts"],
expect: { code: 0 }
},
{
args: ["build", "-o", "tmp", "test/fixtures/test.cjs"],
expect (code, stdout, stderr) {
return stdout.toString().indexOf('tmp/index.cjs') !== -1;
}
},
{
args: ["build", "-o", "tmp", "test/fixtures/test.mjs"],
expect (code, stdout, stderr) {
return stdout.toString().indexOf('tmp/index.js') !== -1;
}
}
]
1 change: 1 addition & 0 deletions test/fixtures/test.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = 'test';
1 change: 1 addition & 0 deletions test/fixtures/test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = 'test';

0 comments on commit 1445ffc

Please sign in to comment.