diff --git a/.travis.yml b/.travis.yml index aa3ee41..1fddbc6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: node_js node_js: - "node" - - "8" - - "6" + - "16" + - "14" diff --git a/LICENSE b/LICENSE index 4b98a41..ae1d937 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright 2016 Bogdan Chadkin +Copyright 2022 Bogdan Chadkin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/README.md b/README.md index 48a75aa..3701932 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,6 @@ * uglify is run in worker for every chunk * errors are displayed with [babel code frame](https://babeljs.io/docs/en/next/babel-code-frame.html) -_Note: uglify-js is able to transpile only es5 syntax. If you want to transpile es6+ syntax use [terser](https://github.com/TrySound/rollup-plugin-terser) instead_ - ## Install ```sh diff --git a/index.js b/index.js index d5db5f0..b7f9165 100644 --- a/index.js +++ b/index.js @@ -4,47 +4,98 @@ const serialize = require("serialize-javascript"); function uglify(userOptions = {}) { if (userOptions.sourceMap != null) { - throw Error("sourceMap option is removed, use sourcemap instead"); + throw Error( + "sourceMap option is removed. Now it is inferred from rollup options." + ); + } + if (userOptions.sourcemap != null) { + throw Error( + "sourcemap option is removed. Now it is inferred from rollup options." + ); } - const normalizedOptions = Object.assign({}, userOptions, { - sourceMap: userOptions.sourcemap !== false - }); + return { + name: "uglify", - ["sourcemap"].forEach(key => { - if (normalizedOptions.hasOwnProperty(key)) { - delete normalizedOptions[key]; - } - }); + async renderChunk(code, chunk, outputOptions) { + if (!this.worker) { + this.worker = new Worker(require.resolve("./transform.js"), { + numWorkers: userOptions.numWorkers, + }); + this.numOfBundles = 0; + } - const minifierOptions = serialize(normalizedOptions); + this.numOfBundles++; - return { - name: "uglify", + const defaultOptions = { + sourceMap: + outputOptions.sourcemap === true || + typeof outputOptions.sourcemap === "string", + }; + if (outputOptions.format === "es" || outputOptions.format === "esm") { + defaultOptions.module = true; + } + if (outputOptions.format === "cjs") { + defaultOptions.toplevel = true; + } - renderStart() { - this.worker = new Worker(require.resolve("./transform.js"), { - numWorkers: userOptions.numWorkers - }); - }, + const normalizedOptions = { ...defaultOptions, ...userOptions }; + + // remove plugin specific options + for (let key of ["numWorkers"]) { + if (normalizedOptions.hasOwnProperty(key)) { + delete normalizedOptions[key]; + } + } + + const serializedOptions = serialize(normalizedOptions); - renderChunk(code) { - return this.worker.transform(code, minifierOptions).catch(error => { + try { + const result = await this.worker.transform(code, serializedOptions); + + if (result.nameCache) { + let { vars, props } = userOptions.nameCache; + + // only assign nameCache.vars if it was provided, and if uglify produced values: + if (vars) { + const newVars = + result.nameCache.vars && result.nameCache.vars.props; + if (newVars) { + vars.props = vars.props || {}; + Object.assign(vars.props, newVars); + } + } + + // support populating an empty nameCache object: + if (!props) { + props = userOptions.nameCache.props = {}; + } + + // merge updated props into original nameCache object: + const newProps = + result.nameCache.props && result.nameCache.props.props; + if (newProps) { + props.props = props.props || {}; + Object.assign(props.props, newProps); + } + } + + return result.result; + } catch (error) { const { message, line, col: column } = error; console.error( codeFrameColumns(code, { start: { line, column } }, { message }) ); throw error; - }); - }, + } finally { + this.numOfBundles--; - generateBundle() { - this.worker.end(); + if (this.numOfBundles === 0) { + this.worker.end(); + this.worker = 0; + } + } }, - - renderError() { - this.worker.end(); - } }; } diff --git a/package.json b/package.json index 3b2f4da..543cd6c 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "transform.js" ], "scripts": { - "test": "jest --runInBand", + "test": "jest", "prepublish": "yarn test" }, "repository": { @@ -24,20 +24,19 @@ "author": "Bogdan Chadkin ", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.0.0", - "jest-worker": "^24.0.0", - "serialize-javascript": "^2.1.2", - "uglify-js": "^3.4.9" + "@babel/code-frame": "^7.10.4", + "jest-worker": "^26.2.1", + "serialize-javascript": "^4.0.0", + "uglify-js": "^3.17.0" }, "peerDependencies": { - "rollup": ">=0.66.0 <2" + "rollup": "^2.0.0" }, "devDependencies": { - "@babel/core": "^7.2.2", - "@babel/preset-env": "^7.3.1", - "babel-jest": "^24.0.0", - "jest": "^24.0.0", - "prettier": "^1.16.1", - "rollup": "^1.27.10" + "@babel/core": "^7.11.1", + "@babel/preset-env": "^7.11.1", + "jest": "^26.2.2", + "prettier": "^2.0.5", + "rollup": "^2.23.1" } } diff --git a/test/__snapshots__/test.js.snap b/test/__snapshots__/test.js.snap index 0071999..09d4000 100644 --- a/test/__snapshots__/test.js.snap +++ b/test/__snapshots__/test.js.snap @@ -2,30 +2,38 @@ exports[`works with code splitting 1`] = ` Object { - "0": Object { - "code": "var chunk1=\\"chunk-1\\";console.log(chunk1); + "chunk-1.js": Object { + "code": "console.log(\\"chunk-1\\"); ", "dynamicImports": Array [], "exports": Array [], "fileName": "chunk-1.js", + "implicitlyLoadedBefore": Array [], + "importedBindings": Object {}, "imports": Array [], "isDynamicEntry": false, "isEntry": true, + "isImplicitEntry": false, "map": null, "name": "chunk-1", + "referencedFiles": Array [], "type": "chunk", }, - "1": Object { - "code": "var chunk2=\\"chunk-2\\";console.log(chunk2); + "chunk-2.js": Object { + "code": "console.log(\\"chunk-2\\"); ", "dynamicImports": Array [], "exports": Array [], "fileName": "chunk-2.js", + "implicitlyLoadedBefore": Array [], + "importedBindings": Object {}, "imports": Array [], "isDynamicEntry": false, "isEntry": true, + "isImplicitEntry": false, "map": null, "name": "chunk-2", + "referencedFiles": Array [], "type": "chunk", }, } diff --git a/test/test.js b/test/test.js index 3b0a960..1c39f36 100644 --- a/test/test.js +++ b/test/test.js @@ -43,9 +43,9 @@ test("minify with sourcemaps", async () => { test("allow to disable source maps", async () => { const bundle = await rollup({ input: "test/fixtures/sourcemap.js", - plugins: [uglify({ sourcemap: false })] + plugins: [uglify()] }); - await bundle.generate({ format: "cjs" }); + await bundle.generate({ format: "cjs", sourcemap: false }); }); test("does not allow to pass sourceMap", async () => { @@ -82,14 +82,16 @@ test("throw error on uglify fail", async () => { test("works with code splitting", async () => { const bundle = await rollup({ input: ["test/fixtures/chunk-1.js", "test/fixtures/chunk-2.js"], - experimentalCodeSplitting: true, plugins: [uglify()] }); const { output } = await bundle.generate({ format: "esm" }); const newOutput = {}; - Object.keys(output).forEach(key => { - const { modules, facadeModuleId, ...value } = output[key]; - newOutput[key] = value; + output.forEach((out) => { + // TODO rewrite with object rest after node 6 dropping + const value = Object.assign({}, out); + delete value.modules; + delete value.facadeModuleId; + newOutput[out.fileName] = value; }); expect(newOutput).toMatchSnapshot(); }); diff --git a/transform.js b/transform.js index 37e643f..30ff4d2 100644 --- a/transform.js +++ b/transform.js @@ -1,13 +1,15 @@ const { minify } = require("uglify-js"); const transform = (code, optionsString) => { - const options = eval(`(${optionsString})`) - const result = minify(code, options); - if (result.error) { - throw result.error; - } else { - return result; - } + const options = eval(`(${optionsString})`); + return new Promise((resolve, reject) => { + const result = minify(code, options); + if (result.error) { + reject(result.error); + } else { + resolve({ result, nameCache: options.nameCache }); + } + }); }; exports.transform = transform;