From 866e5dcc9b8d11ec34e7fc61f785f35823acd42c Mon Sep 17 00:00:00 2001 From: Denis Bardadym Date: Thu, 31 Oct 2019 14:39:33 +0100 Subject: [PATCH] chore: add rollup-plugin-inject (#19) * initial version * Update test to snapshot testing * Disable extra eslint rules * Replace for-of to forEach * Update packages/inject/CHANGELOG.md Co-Authored-By: Andrew Powell * Update packages/inject/package.json Co-Authored-By: Andrew Powell * Update packages/inject/README.md Co-Authored-By: Andrew Powell * Update packages/inject/package.json Co-Authored-By: Andrew Powell * Update packages/inject/README.md Co-Authored-By: Andrew Powell * chore: Update packages/inject/package.json * chore: Update packages/inject/package.json * chore: Apply suggestions from code review * chore: bump package major --- packages/inject/CHANGELOG.md | 54 +++++ packages/inject/README.md | 99 +++++++++ packages/inject/index.d.ts | 32 +++ packages/inject/package.json | 71 ++++++ packages/inject/rollup.config.js | 12 + packages/inject/src/index.js | 207 ++++++++++++++++++ packages/inject/test/fixtures/.eslintrc | 10 + packages/inject/test/fixtures/basic/input.js | 3 + .../inject/test/fixtures/existing/input.js | 5 + .../test/fixtures/import-namespace/input.js | 2 + .../inject/test/fixtures/keypaths/input.js | 4 + .../keypaths/polyfills/object-assign.js | 8 + packages/inject/test/fixtures/named/input.js | 1 + packages/inject/test/fixtures/non-js/foo.es6 | 1 + packages/inject/test/fixtures/non-js/input.js | 4 + .../inject/test/fixtures/non-js/styles.css | 4 + .../test/fixtures/redundant-keys/input.js | 1 + .../inject/test/fixtures/shadowing/input.js | 7 + .../inject/test/fixtures/shorthand/input.js | 2 + packages/inject/test/snapshots/test.js.md | 111 ++++++++++ packages/inject/test/snapshots/test.js.snap | Bin 0 -> 789 bytes packages/inject/test/test.js | 72 ++++++ packages/inject/test/types.ts | 27 +++ pnpm-lock.yaml | 86 ++++++-- 24 files changed, 799 insertions(+), 24 deletions(-) create mode 100644 packages/inject/CHANGELOG.md create mode 100644 packages/inject/README.md create mode 100644 packages/inject/index.d.ts create mode 100644 packages/inject/package.json create mode 100644 packages/inject/rollup.config.js create mode 100644 packages/inject/src/index.js create mode 100644 packages/inject/test/fixtures/.eslintrc create mode 100644 packages/inject/test/fixtures/basic/input.js create mode 100644 packages/inject/test/fixtures/existing/input.js create mode 100644 packages/inject/test/fixtures/import-namespace/input.js create mode 100644 packages/inject/test/fixtures/keypaths/input.js create mode 100644 packages/inject/test/fixtures/keypaths/polyfills/object-assign.js create mode 100644 packages/inject/test/fixtures/named/input.js create mode 100644 packages/inject/test/fixtures/non-js/foo.es6 create mode 100644 packages/inject/test/fixtures/non-js/input.js create mode 100644 packages/inject/test/fixtures/non-js/styles.css create mode 100644 packages/inject/test/fixtures/redundant-keys/input.js create mode 100644 packages/inject/test/fixtures/shadowing/input.js create mode 100644 packages/inject/test/fixtures/shorthand/input.js create mode 100644 packages/inject/test/snapshots/test.js.md create mode 100644 packages/inject/test/snapshots/test.js.snap create mode 100644 packages/inject/test/test.js create mode 100644 packages/inject/test/types.ts diff --git a/packages/inject/CHANGELOG.md b/packages/inject/CHANGELOG.md new file mode 100644 index 000000000..5aefb5fca --- /dev/null +++ b/packages/inject/CHANGELOG.md @@ -0,0 +1,54 @@ +# @rollup/plugin-inject Changelog + +## 3.0.2 + +* Fix bug with sourcemap usage + +## 3.0.1 + +* Generate sourcemap when sourcemap enabled + +## 3.0.0 + +* Remove node v6 from support +* Use modern js + +## 2.1.0 + +* Update all dependencies ([#15](https://github.com/rollup/rollup-plugin-inject/pull/15)) + +## 2.0.0 + +* Work with all file extensions, not just `.js` (unless otherwise specified via `options.include` and `options.exclude`) ([#6](https://github.com/rollup/rollup-plugin-inject/pull/6)) +* Allow `*` imports ([#9](https://github.com/rollup/rollup-plugin-inject/pull/9)) +* Ignore replacements that are superseded (e.g. if `Buffer.isBuffer` is replaced, ignore `Buffer` replacement) ([#10](https://github.com/rollup/rollup-plugin-inject/pull/10)) + +## 1.4.1 + +* Return a `name` + +## 1.4.0 + +* Use `string.search` instead of `regex.test` to avoid state-related mishaps ([#5](https://github.com/rollup/rollup-plugin-inject/issues/5)) +* Prevent self-importing module bug + +## 1.3.0 + +* Windows support ([#2](https://github.com/rollup/rollup-plugin-inject/issues/2)) +* Node 0.12 support + +## 1.2.0 + +* Generate sourcemaps by default + +## 1.1.1 + +* Use `modules` option + +## 1.1.0 + +* Handle shorthand properties + +## 1.0.0 + +* First release diff --git a/packages/inject/README.md b/packages/inject/README.md new file mode 100644 index 000000000..5cd1f6adc --- /dev/null +++ b/packages/inject/README.md @@ -0,0 +1,99 @@ +[cover]: https://codecov.io/gh/rollup/plugins/inject/branch/master/graph/badge.svg +[cover-url]: https://codecov.io/gh/rollup/plugins +[size]: https://packagephobia.now.sh/badge?p=@rollup/plugin-inject +[size-url]: https://packagephobia.now.sh/result?p=@rollup/plugin-inject +[tests]: https://img.shields.io/circleci/project/github/rollup/plugins.svg +[tests-url]: https://circleci.com/gh/rollup/plugins + +[![tests][tests]][tests-url] +[![cover][cover]][cover-url] +[![size][size]][size-url] +[![libera manifesto](https://img.shields.io/badge/libera-manifesto-lightgrey.svg)](https://liberamanifesto.com) + +# @rollup/plugin-inject + +🍣 A Rollup plugin which scans modules for global variables and injects `import` statements where necessary. + +## Requirements + +This plugin requires an [LTS](https://github.com/nodejs/Release) Node version (v8.0.0+) and Rollup v1.20.0+. + +## Install + +Using npm: + +```console +npm install @rollup/plugin-inject --save-dev +``` + +## Usage + +Create a `rollup.config.js` [configuration file](https://www.rollupjs.org/guide/en/#configuration-files) and import the plugin: + +```js +import inject from '@rollup/plugin-inject'; + +export default { + input: 'src/index.js', + output: { + dir: 'output', + format: 'cjs' + }, + plugins: [ + inject({ + Promise: ['es6-promise', 'Promise'] + }) + ] +}; +``` + +Then call `rollup` either via the [CLI](https://www.rollupjs.org/guide/en/#command-line-reference) or the [API](https://www.rollupjs.org/guide/en/#javascript-api). + +This configuration above will scan all your files for global Promise usage and plugin will add import to desired module (`import { Promise } from 'es6-promise'` in this case). + +Examples: + +```js +{ + // import { Promise } from 'es6-promise' + Promise: [ 'es6-promise', 'Promise' ], + + // import { Promise as P } from 'es6-promise' + P: [ 'es6-promise', 'Promise' ], + + // import $ from 'jquery' + $: 'jquery', + + // import * as fs from 'fs' + fs: [ 'fs', '*' ], + + // use a local module instead of a third-party one + 'Object.assign': path.resolve( 'src/helpers/object-assign.js' ), +} +``` + +Typically, `@rollup/plugin-inject` should be placed in `plugins` _before_ other plugins so that they may apply optimizations, such as dead code removal. + +## Options + +In addition to the properties and values specified for injecting, users may also specify the options below. + +### `exclude` + +Type: `String` | `Array[...String]` +Default: `null` + +A [minimatch pattern](https://github.com/isaacs/minimatch), or array of patterns, which specifies the files in the build the plugin should _ignore_. By default no files are ignored. + +### `include` + +Type: `String` | `Array(String)` +Default: `null` + +A [minimatch pattern](https://github.com/isaacs/minimatch), or array of patterns, which specifies the files in the build the plugin should operate on. By default all files are targeted. + +## Meta + +[CONTRIBUTING](/.github/CONTRIBUTING.md) + +[LICENSE (MIT)](/LICENSE) diff --git a/packages/inject/index.d.ts b/packages/inject/index.d.ts new file mode 100644 index 000000000..421e88a37 --- /dev/null +++ b/packages/inject/index.d.ts @@ -0,0 +1,32 @@ +import { Plugin } from "rollup"; + +type Injectment = string | [string, string]; + +interface RollupInjectOptions { + /** + * A minimatch pattern, or array of patterns, of files that should be + * processed by this plugin (if omitted, all files are included by default) + */ + include?: string | RegExp | ReadonlyArray | null; + + /** + * Files that should be excluded, if `include` is otherwise too permissive. + */ + exclude?: string | RegExp | ReadonlyArray | null; + + /** + * You can separate values to inject from other options. + */ + modules?: { [str: string]: Injectment }; + + /** + * All other options are treated as `string: injectment` injectrs, + * or `string: (id) => injectment` functions. + */ + [str: string]: Injectment | RollupInjectOptions["include"] | RollupInjectOptions["modules"]; +} + +/** + * inject strings in files while bundling them. + */ +export default function inject(options?: RollupInjectOptions): Plugin; diff --git a/packages/inject/package.json b/packages/inject/package.json new file mode 100644 index 000000000..f00126155 --- /dev/null +++ b/packages/inject/package.json @@ -0,0 +1,71 @@ +{ + "name": "@rollup/plugin-inject", + "version": "4.0.0", + "publishConfig": { + "access": "public" + }, + "license": "MIT", + "repository": "rollup/plugins", + "author": "Rich Harris ", + "homepage": "https://github.com/rollup/plugins", + "bugs": "https://github.com/rollup/plugins/issues", + "main": "dist/index.js", + "scripts": { + "build": "rollup -c", + "ci:coverage": "nyc pnpm run test && nyc report --reporter=text-lcov > coverage.lcov", + "ci:coverage:submit": "curl -s https://codecov.io/bash | bash -s - -F inject", + "ci:lint": "pnpm run build && pnpm run lint && pnpm run security", + "ci:lint:commits": "commitlint --from=${CIRCLE_BRANCH} --to=${CIRCLE_SHA1}", + "ci:test": "pnpm run test -- --verbose && pnpm run test:ts", + "lint": "pnpm run lint:js && pnpm run lint:docs && pnpm run lint:package", + "lint:docs": "prettier --single-quote --write README.md", + "lint:js": "eslint --fix --cache src test", + "lint:package": "prettier --write package.json --plugin=prettier-plugin-package", + "prebuild": "del-cli dist", + "prepare": "npm run build", + "prepublishOnly": "npm run lint && npm run test", + "pretest": "npm run build", + "security": "echo 'pnpm needs `npm audit` support'", + "test": "ava", + "test:ts": "tsc index.d.ts test/types.ts --noEmit" + }, + "files": [ + "dist", + "index.d.ts", + "LICENSE", + "README.md" + ], + "keywords": [ + "rollup", + "plugin", + "inject", + "es2015", + "npm", + "modules" + ], + "peerDependencies": { + "rollup": "^1.20.0" + }, + "dependencies": { + "estree-walker": "^0.9.0", + "magic-string": "^0.25.2", + "rollup-pluginutils": "^2.6.0" + }, + "devDependencies": { + "del-cli": "^3.0.0", + "locate-character": "^2.0.5", + "rollup": "^1.20.0", + "rollup-plugin-buble": "^0.19.6", + "source-map": "^0.7.3", + "typescript": "^3.4.3" + }, + "ava": { + "files": [ + "!**/fixtures/**", + "!**/helpers/**", + "!**/recipes/**", + "!**/types.ts" + ] + }, + "module": "dist/index.es.js" +} diff --git a/packages/inject/rollup.config.js b/packages/inject/rollup.config.js new file mode 100644 index 000000000..4b8bb96b9 --- /dev/null +++ b/packages/inject/rollup.config.js @@ -0,0 +1,12 @@ +import buble from "rollup-plugin-buble"; + +import pkg from "./package.json"; + +const external = Object.keys(pkg.dependencies).concat("path"); + +export default { + input: "src/index.js", + plugins: [buble()], + external, + output: [{ file: pkg.main, format: "cjs" }, { file: pkg.module, format: "es" }] +}; diff --git a/packages/inject/src/index.js b/packages/inject/src/index.js new file mode 100644 index 000000000..6b0d2db9c --- /dev/null +++ b/packages/inject/src/index.js @@ -0,0 +1,207 @@ +import { sep } from 'path'; + +import { attachScopes, createFilter, makeLegalIdentifier } from 'rollup-pluginutils'; +import { walk } from 'estree-walker'; + +import MagicString from 'magic-string'; + +const escape = (str) => str.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&'); + +const isReference = (node, parent) => { + if (node.type === 'MemberExpression') { + return !node.computed && isReference(node.object, node); + } + + if (node.type === 'Identifier') { + // TODO is this right? + if (parent.type === 'MemberExpression') return parent.computed || node === parent.object; + + // disregard the `bar` in { bar: foo } + if (parent.type === 'Property' && node !== parent.value) return false; + + // disregard the `bar` in `class Foo { bar () {...} }` + if (parent.type === 'MethodDefinition') return false; + + // disregard the `bar` in `export { foo as bar }` + if (parent.type === 'ExportSpecifier' && node !== parent.local) return false; + + return true; + } + + return false; +}; + +const flatten = (startNode) => { + const parts = []; + let node = startNode; + + while (node.type === 'MemberExpression') { + parts.unshift(node.property.name); + node = node.object; + } + + const { name } = node; + parts.unshift(name); + + return { name, keypath: parts.join('.') }; +}; + +export default function inject(options) { + if (!options) throw new Error('Missing options'); + + const filter = createFilter(options.include, options.exclude); + + let { modules } = options; + + if (!modules) { + modules = Object.assign({}, options); + delete modules.include; + delete modules.exclude; + delete modules.sourceMap; + delete modules.sourcemap; + } + + const modulesMap = new Map(Object.entries(modules)); + + // Fix paths on Windows + if (sep !== '/') { + modulesMap.forEach((mod, key) => { + modulesMap.set( + key, + Array.isArray(mod) ? [mod[0].split(sep).join('/'), mod[1]] : mod.split(sep).join('/') + ); + }); + } + + const firstpass = new RegExp( + `(?:${Array.from(modulesMap.keys()) + .map(escape) + .join('|')})`, + 'g' + ); + const sourceMap = options.sourceMap !== false && options.sourcemap !== false; + + return { + name: 'inject', + + transform(code, id) { + if (!filter(id)) return null; + if (code.search(firstpass) === -1) return null; + + if (sep !== '/') id = id.split(sep).join('/'); // eslint-disable-line no-param-reassign + + let ast = null; + try { + ast = this.parse(code); + } catch (err) { + this.warn({ + code: 'PARSE_ERROR', + message: `rollup-plugin-inject: failed to parse ${id}. Consider restricting the plugin to particular files via options.include` + }); + } + if (!ast) { + return null; + } + + const imports = new Set(); + ast.body.forEach((node) => { + if (node.type === 'ImportDeclaration') { + node.specifiers.forEach((specifier) => { + imports.add(specifier.local.name); + }); + } + }); + + // analyse scopes + let scope = attachScopes(ast, 'scope'); + + const magicString = new MagicString(code); + + const newImports = new Map(); + + function handleReference(node, name, keypath) { + let mod = modulesMap.get(keypath); + if (mod && !imports.has(name) && !scope.contains(name)) { + if (typeof mod === 'string') mod = [mod, 'default']; + + // prevent module from importing itself + if (mod[0] === id) return false; + + const hash = `${keypath}:${mod[0]}:${mod[1]}`; + + const importLocalName = + name === keypath ? name : makeLegalIdentifier(`$inject_${keypath}`); + + if (!newImports.has(hash)) { + if (mod[1] === '*') { + newImports.set(hash, `import * as ${importLocalName} from '${mod[0]}';`); + } else { + newImports.set(hash, `import { ${mod[1]} as ${importLocalName} } from '${mod[0]}';`); + } + } + + if (name !== keypath) { + magicString.overwrite(node.start, node.end, importLocalName, { + storeName: true + }); + } + + return true; + } + + return false; + } + + walk(ast, { + enter(node, parent) { + if (sourceMap) { + magicString.addSourcemapLocation(node.start); + magicString.addSourcemapLocation(node.end); + } + + if (node.scope) { + scope = node.scope; // eslint-disable-line prefer-destructuring + } + + // special case – shorthand properties. because node.key === node.value, + // we can't differentiate once we've descended into the node + if (node.type === 'Property' && node.shorthand) { + const { name } = node.key; + handleReference(node, name, name); + this.skip(); + return; + } + + if (isReference(node, parent)) { + const { name, keypath } = flatten(node); + const handled = handleReference(node, name, keypath); + if (handled) { + this.skip(); + } + } + }, + leave(node) { + if (node.scope) { + scope = scope.parent; + } + } + }); + + if (newImports.size === 0) { + return { + code, + ast, + map: sourceMap ? magicString.generateMap({ hires: true }) : null + }; + } + const importBlock = Array.from(newImports.values()).join('\n\n'); + + magicString.prepend(`${importBlock}\n\n`); + + return { + code: magicString.toString(), + map: sourceMap ? magicString.generateMap({ hires: true }) : null + }; + } + }; +} diff --git a/packages/inject/test/fixtures/.eslintrc b/packages/inject/test/fixtures/.eslintrc new file mode 100644 index 000000000..06aab08cc --- /dev/null +++ b/packages/inject/test/fixtures/.eslintrc @@ -0,0 +1,10 @@ +{ + "rules": { + "no-undef": "off", + "no-console": "off", + "import/no-unresolved": "off", + "import/extensions": "off", + "func-names": "off", + "no-param-reassign": "off" + } +} diff --git a/packages/inject/test/fixtures/basic/input.js b/packages/inject/test/fixtures/basic/input.js new file mode 100644 index 000000000..3d96316db --- /dev/null +++ b/packages/inject/test/fixtures/basic/input.js @@ -0,0 +1,3 @@ +$(() => { + console.log('ready'); +}); diff --git a/packages/inject/test/fixtures/existing/input.js b/packages/inject/test/fixtures/existing/input.js new file mode 100644 index 000000000..02a69157e --- /dev/null +++ b/packages/inject/test/fixtures/existing/input.js @@ -0,0 +1,5 @@ +import $ from 'jquery'; + +$(() => { + console.log('ready'); +}); diff --git a/packages/inject/test/fixtures/import-namespace/input.js b/packages/inject/test/fixtures/import-namespace/input.js new file mode 100644 index 000000000..49df6d22f --- /dev/null +++ b/packages/inject/test/fixtures/import-namespace/input.js @@ -0,0 +1,2 @@ +console.log(foo.bar); +console.log(foo.baz); diff --git a/packages/inject/test/fixtures/keypaths/input.js b/packages/inject/test/fixtures/keypaths/input.js new file mode 100644 index 000000000..311a88dc0 --- /dev/null +++ b/packages/inject/test/fixtures/keypaths/input.js @@ -0,0 +1,4 @@ +const original = { foo: 'bar' }; +const clone = Object.assign({}, original); + +export default clone; diff --git a/packages/inject/test/fixtures/keypaths/polyfills/object-assign.js b/packages/inject/test/fixtures/keypaths/polyfills/object-assign.js new file mode 100644 index 000000000..11bb2ce89 --- /dev/null +++ b/packages/inject/test/fixtures/keypaths/polyfills/object-assign.js @@ -0,0 +1,8 @@ +export default Object.assign || + function(target, ...sources) { + sources.forEach((source) => { + Object.keys(source).forEach((key) => (target[key] = source[key])); + }); + + return target; + }; diff --git a/packages/inject/test/fixtures/named/input.js b/packages/inject/test/fixtures/named/input.js new file mode 100644 index 000000000..86beb2569 --- /dev/null +++ b/packages/inject/test/fixtures/named/input.js @@ -0,0 +1 @@ +Promise.all([thisThing, thatThing]).then(() => someOtherThing); diff --git a/packages/inject/test/fixtures/non-js/foo.es6 b/packages/inject/test/fixtures/non-js/foo.es6 new file mode 100644 index 000000000..bf0dcfba3 --- /dev/null +++ b/packages/inject/test/fixtures/non-js/foo.es6 @@ -0,0 +1 @@ +export default relative( 'foo/bar', 'foo/baz' ); diff --git a/packages/inject/test/fixtures/non-js/input.js b/packages/inject/test/fixtures/non-js/input.js new file mode 100644 index 000000000..848f0595b --- /dev/null +++ b/packages/inject/test/fixtures/non-js/input.js @@ -0,0 +1,4 @@ +import './styles.css'; +import foo from './foo.es6'; + +assert.equal(foo, path.join('..', 'baz')); diff --git a/packages/inject/test/fixtures/non-js/styles.css b/packages/inject/test/fixtures/non-js/styles.css new file mode 100644 index 000000000..3e05acec5 --- /dev/null +++ b/packages/inject/test/fixtures/non-js/styles.css @@ -0,0 +1,4 @@ +body { + position: relative; + font-family: 'Comic Sans MS'; +} diff --git a/packages/inject/test/fixtures/redundant-keys/input.js b/packages/inject/test/fixtures/redundant-keys/input.js new file mode 100644 index 000000000..f2261e2cd --- /dev/null +++ b/packages/inject/test/fixtures/redundant-keys/input.js @@ -0,0 +1 @@ +Buffer.isBuffer('foo'); diff --git a/packages/inject/test/fixtures/shadowing/input.js b/packages/inject/test/fixtures/shadowing/input.js new file mode 100644 index 000000000..969731629 --- /dev/null +++ b/packages/inject/test/fixtures/shadowing/input.js @@ -0,0 +1,7 @@ +function launch($) { + $(() => { + console.log('ready'); + }); +} + +launch((fn) => fn()); diff --git a/packages/inject/test/fixtures/shorthand/input.js b/packages/inject/test/fixtures/shorthand/input.js new file mode 100644 index 000000000..38b7e35e7 --- /dev/null +++ b/packages/inject/test/fixtures/shorthand/input.js @@ -0,0 +1,2 @@ +const polyfills = { Promise }; +polyfills.Promise.resolve().then(() => 'it works'); diff --git a/packages/inject/test/snapshots/test.js.md b/packages/inject/test/snapshots/test.js.md new file mode 100644 index 000000000..1abf72595 --- /dev/null +++ b/packages/inject/test/snapshots/test.js.md @@ -0,0 +1,111 @@ +# Snapshot report for `test/test.js` + +The actual snapshot is saved in `test.js.snap`. + +Generated by [AVA](https://ava.li). + +## generates * imports + +> Snapshot 1 + + `import * as foo from 'foo';␊ + ␊ + console.log(foo.bar);␊ + console.log(foo.baz);␊ + ` + +## handles redundant keys + +> Snapshot 1 + + `import { default as $inject_Buffer_isBuffer } from 'is-buffer';␊ + ␊ + $inject_Buffer_isBuffer('foo');␊ + ` + +## handles shadowed variables + +> Snapshot 1 + + `function launch($) {␊ + $(() => {␊ + console.log('ready');␊ + });␊ + }␊ + ␊ + launch((fn) => fn());␊ + ` + +## handles shorthand properties + +> Snapshot 1 + + `import { Promise as Promise } from 'es6-promise';␊ + ␊ + const polyfills = { Promise };␊ + polyfills.Promise.resolve().then(() => 'it works');␊ + ` + +## ignores existing imports + +> Snapshot 1 + + `import $ from 'jquery';␊ + ␊ + $(() => {␊ + console.log('ready');␊ + });␊ + ` + +## inserts a default import statement + +> Snapshot 1 + + `import { default as $ } from 'jquery';␊ + ␊ + $(() => {␊ + console.log('ready');␊ + });␊ + ` + +## inserts a named import statement + +> Snapshot 1 + + `import { Promise as Promise } from 'es6-promise';␊ + ␊ + Promise.all([thisThing, thatThing]).then(() => someOtherThing);␊ + ` + +## overwrites keypaths + +> Snapshot 1 + + `import { default as $inject_Object_assign } from 'fixtures/keypaths/polyfills/object-assign.js';␊ + ␊ + const original = { foo: 'bar' };␊ + const clone = $inject_Object_assign({}, original);␊ + ␊ + export default clone;␊ + ` + +## transpiles non-JS files but handles failures to parse + +> Snapshot 1 + + `import './styles.css';␊ + import foo from './foo.es6';␊ + ␊ + assert.equal(foo, path.join('..', 'baz'));␊ + ` + +## uses the modules property + +> Snapshot 1 + + `import { default as $ } from 'jquery';␊ + ␊ + $(() => {␊ + console.log('ready');␊ + });␊ + ` diff --git a/packages/inject/test/snapshots/test.js.snap b/packages/inject/test/snapshots/test.js.snap new file mode 100644 index 0000000000000000000000000000000000000000..f4d1d7047fafa8d8182de21ebfbfe15a0a4c47a0 GIT binary patch literal 789 zcmV+w1M2)iRzVxEk00000000BM zQeQ|EK^)#ai)0JZAR~;Nhl|~la}N|lyHfgtWJG6_2AN%Z=j`2_>(03|r=E5XVF^a~ zVnI<474*6&6HT(d^vbRoB%+LA-|x>SiGm;r zo99P9JsYkrYb>pMRnq-JWgz?QWAW&7)kOEU{inCqe3&wdR_+oxI?m$uz^v5REH>#= z@!-|58<$0n*0R(y;7!Zx=8c1u=gwc6?3i33a&);%5RQ-Txl(_uVR-do*QsTPwoi*3 z4Y7DxTTnQ@q-D6=)f5;wt6vZ~dWXf)p^}SLA0HM!7&_9TOuVlVIT|bwgj=NnYi_!5 zXY;$cgJZ2T^CCy@v$(eO?bp$}4eMvN(C5WBzi1*y*DMf(p6Z^mwpCAxX0M-keDdkx zN|B=@EDD50ylZixP%Q31opEeY&=1<7t|bf#Gy>cp1#}B{0y)-|fL2oWOOjhrJYYi| z=$8NpW0PP5ss@fIvIVvFr0nraDHi{Yn`F707#Ska=FS{53XrU=h^H}I#<^BC!%$jj z6p`IgWJbJzMm5ThZ61|Iq3KjZa3>6~Z;RjM6yUliww9)Mx(?R$3{{|hCzIoXH=v!)EKb~wr4>Bwon9_ngKR2Egj=^Kn`h^3{rmQ zLD;}1WVd<6mHw1BOTn6uU@tE_Q#4P`8E(DPn)_dDYOkI!!xUi?7#cf86}N|{|48*a zG5~D4lq5N+if-~8-Bdie3mFvt!V>8LX1BmcoK5jd*$Xw}9kxAebVKDQr^twc9&B|G zIk$Q3PbS%v&~<195lJ7iH6l_G;s~F3{>O^UobZcWqa#;t%diI--?67ovzZw)NM$ba z`17EBxwHoyT3PiGnlvC$!^AeI1AiA%eKrG|u`S2^2Q8|?u7qaT%wAx7UX5X7Dzd7| TUfaigvc1S}RUroIodo~@KyPuz literal 0 HcmV?d00001 diff --git a/packages/inject/test/test.js b/packages/inject/test/test.js new file mode 100644 index 000000000..8e36c29fb --- /dev/null +++ b/packages/inject/test/test.js @@ -0,0 +1,72 @@ +const fs = require('fs'); +const path = require('path'); + +const acorn = require('acorn'); +const test = require('ava'); + +const inject = require('..'); + +const compare = (t, fixture, options) => { + const filename = path.resolve(`test/fixtures/${fixture}/input.js`); + const input = fs.readFileSync(filename, 'utf-8'); + const output = inject(options).transform.call( + { + parse: (code) => + acorn.parse(code, { + sourceType: 'module', + ecmaVersion: 9 + }) + }, + input, + filename + ); + + t.snapshot(output ? output.code : input); +}; + +test('inserts a default import statement', (t) => { + compare(t, 'basic', { $: 'jquery' }); +}); + +test('uses the modules property', (t) => { + compare(t, 'basic', { + modules: { $: 'jquery' } + }); +}); + +test('inserts a named import statement', (t) => { + compare(t, 'named', { Promise: ['es6-promise', 'Promise'] }); +}); + +test('overwrites keypaths', (t) => { + compare(t, 'keypaths', { + 'Object.assign': 'fixtures/keypaths/polyfills/object-assign.js' + }); +}); + +test('ignores existing imports', (t) => { + compare(t, 'existing', { $: 'jquery' }); +}); + +test('handles shadowed variables', (t) => { + compare(t, 'shadowing', { $: 'jquery' }); +}); + +test('handles shorthand properties', (t) => { + compare(t, 'shorthand', { Promise: ['es6-promise', 'Promise'] }); +}); + +test('handles redundant keys', (t) => { + compare(t, 'redundant-keys', { + Buffer: 'Buffer', + 'Buffer.isBuffer': 'is-buffer' + }); +}); + +test('generates * imports', (t) => { + compare(t, 'import-namespace', { foo: ['foo', '*'] }); +}); + +test('transpiles non-JS files but handles failures to parse', (t) => { + compare(t, 'non-js', { relative: ['path', 'relative'] }); +}); diff --git a/packages/inject/test/types.ts b/packages/inject/test/types.ts new file mode 100644 index 000000000..4e383acc6 --- /dev/null +++ b/packages/inject/test/types.ts @@ -0,0 +1,27 @@ +// @ts-check +import { dirname } from "path"; + +import inject from ".."; + +/** @type {import("rollup").RollupOptions} */ +const config = { + input: "main.js", + output: { + file: "bundle.js", + format: "iife" + }, + plugins: [ + inject({ + include: "config.js", + exclude: "node_modules/**", + Promise: ["es6-promise", "Promise"], + $: "jquery", + modules: { + Promise: ["es6-promise", "Promise"], + $: "jquery" + } + }) + ] +}; + +export default config; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9dfbc16de..838e75de2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,7 +23,7 @@ importers: execa: ^2.0.4 lint-staged: ^9.2.0 nyc: ^14.1.1 - pnpm: ^4.0.0 + pnpm: ^4.1.5 pre-commit: ^1.2.2 prettier: ^1.18.2 prettier-plugin-package: ^0.3.1 @@ -65,6 +65,28 @@ importers: rollup-pluginutils: ^2.6.0 source-map: ^0.7.3 typescript: ^3.4.3 + packages/inject: + dependencies: + estree-walker: 0.9.0 + magic-string: 0.25.4 + rollup-pluginutils: 2.8.2 + typescript: 3.6.4 + devDependencies: + del-cli: 3.0.0 + locate-character: 2.0.5 + rollup: 1.26.0 + rollup-plugin-buble: 0.19.8 + source-map: 0.7.3 + specifiers: + del-cli: ^3.0.0 + estree-walker: ^0.9.0 + locate-character: ^2.0.5 + magic-string: ^0.25.2 + rollup: ^1.20.0 + rollup-plugin-buble: ^0.19.6 + rollup-pluginutils: ^2.6.0 + source-map: ^0.7.3 + typescript: ^3.4.3 packages/replace: dependencies: magic-string: 0.25.4 @@ -85,6 +107,29 @@ importers: rollup-pluginutils: ^2.6.0 source-map: ^0.7.3 typescript: ^3.4.3 + packages/strip: + dependencies: + estree-walker: 0.6.1 + magic-string: 0.25.4 + rollup-pluginutils: 2.8.2 + devDependencies: + acorn: 6.3.0 + rollup: 1.26.0 + specifiers: + acorn: ^6.0.2 + estree-walker: ^0.6.0 + magic-string: ^0.25.1 + rollup: ^1.20.0 + rollup-pluginutils: ^2.8.1 + packages/wasm: + devDependencies: + del-cli: 3.0.0 + rollup: 1.26.0 + source-map: 0.7.3 + specifiers: + del-cli: ^3.0.0 + rollup: ^1.20.0 + source-map: ^0.7.3 lockfileVersion: 5.1 packages: /@ava/babel-plugin-throws-helper/4.0.0: @@ -439,7 +484,7 @@ packages: dependencies: '@types/events': 3.0.0 '@types/minimatch': 3.0.3 - '@types/node': 12.11.1 + '@types/node': 12.11.7 dev: true resolution: integrity: sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w== @@ -451,6 +496,10 @@ packages: dev: true resolution: integrity: sha512-TJtwsqZ39pqcljJpajeoofYRfeZ7/I/OMUQ5pR4q5wOKf2ocrUvBAZUMhWsOvKx3dVc/aaV5GluBivt0sWqA5A== + /@types/node/12.11.7: + dev: true + resolution: + integrity: sha512-JNbGaHFCLwgHn/iCckiGSOZ1XYHsKFwREtzPwSGCVld1SGhOlmZw2D4ZI94HQCrBHbADzW9m4LER/8olJTRGHA== /@types/node/12.12.0: dev: true resolution: @@ -1378,7 +1427,7 @@ packages: /del/5.1.0: dependencies: globby: 10.0.1 - graceful-fs: 4.2.2 + graceful-fs: 4.2.3 is-glob: 4.0.1 is-path-cwd: 2.2.0 is-path-inside: 3.0.2 @@ -1720,6 +1769,10 @@ packages: /estree-walker/0.6.1: resolution: integrity: sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w== + /estree-walker/0.9.0: + dev: false + resolution: + integrity: sha512-12U47o7XHUX329+x3FzNVjCx3SHEzMF0nkDv7r/HnBzX/xNTKxajBk6gyygaxrAFtLj39219oMfbtxv4KpaOiA== /esutils/2.0.3: dev: true engines: @@ -1964,17 +2017,6 @@ packages: node: '>= 6' resolution: integrity: sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw== - /glob/7.1.4: - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.0.4 - once: 1.4.0 - path-is-absolute: 1.0.1 - dev: true - resolution: - integrity: sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A== /glob/7.1.5: dependencies: fs.realpath: 1.0.0 @@ -2006,7 +2048,7 @@ packages: array-union: 2.1.0 dir-glob: 3.0.1 fast-glob: 3.1.0 - glob: 7.1.4 + glob: 7.1.5 ignore: 5.1.4 merge2: 1.3.0 slash: 3.0.0 @@ -2045,10 +2087,6 @@ packages: node: '>=8.6' resolution: integrity: sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== - /graceful-fs/4.2.2: - dev: true - resolution: - integrity: sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q== /graceful-fs/4.2.3: dev: true resolution: @@ -2714,7 +2752,7 @@ packages: integrity: sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg= /load-json-file/4.0.0: dependencies: - graceful-fs: 4.2.2 + graceful-fs: 4.2.3 parse-json: 4.0.0 pify: 3.0.0 strip-bom: 3.0.0 @@ -3759,7 +3797,7 @@ packages: dependencies: regenerate: 1.4.0 regenerate-unicode-properties: 8.1.0 - regjsgen: 0.5.0 + regjsgen: 0.5.1 regjsparser: 0.6.0 unicode-match-property-ecmascript: 1.0.4 unicode-match-property-value-ecmascript: 1.1.0 @@ -3784,9 +3822,9 @@ packages: node: '>=8' resolution: integrity: sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw== - /regjsgen/0.5.0: + /regjsgen/0.5.1: resolution: - integrity: sha512-RnIrLhrXCX5ow/E5/Mh2O4e/oa1/jW0eaBKTSy3LaCj+M3Bqvm97GWDp2yUtzIs4LEn65zR2yiYGFqb2ApnzDA== + integrity: sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg== /regjsparser/0.6.0: dependencies: jsesc: 0.5.0 @@ -3896,7 +3934,7 @@ packages: integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== /rimraf/3.0.0: dependencies: - glob: 7.1.4 + glob: 7.1.5 dev: true hasBin: true resolution: