From 0af323af966fae5c1b2356d441b7a91dff1e5315 Mon Sep 17 00:00:00 2001 From: Chris Trevino Date: Mon, 2 Dec 2019 16:05:43 -0800 Subject: [PATCH] feat(replace): allow plugin to operate as an output plugin (#55) * add execution hook to replace * add tests to replacement * break apart the executeReplacements function * move sourcemap tests to separate module, test out different configurations * fix(tests): correct sourcemaps test * clean up eslint-disable directives --- packages/replace/src/index.js | 61 +++--- packages/replace/test/misc.js | 74 ++----- .../replace/test/snapshots/sourcemaps.js.md | 185 ++++++++++++++++++ .../replace/test/snapshots/sourcemaps.js.snap | Bin 0 -> 435 bytes packages/replace/test/sourcemaps.js | 127 ++++++++++++ 5 files changed, 364 insertions(+), 83 deletions(-) create mode 100644 packages/replace/test/snapshots/sourcemaps.js.md create mode 100644 packages/replace/test/snapshots/sourcemaps.js.snap create mode 100644 packages/replace/test/sourcemaps.js diff --git a/packages/replace/src/index.js b/packages/replace/src/index.js index 482ee9ddd..0d15403e9 100755 --- a/packages/replace/src/index.js +++ b/packages/replace/src/index.js @@ -42,7 +42,6 @@ export default function replace(options = {}) { const keys = Object.keys(functionValues) .sort(longest) .map(escape); - const pattern = delimiters ? new RegExp(`${escape(delimiters[0])}(${keys.join('|')})${escape(delimiters[1])}`, 'g') : new RegExp(`\\b(${keys.join('|')})\\b`, 'g'); @@ -50,36 +49,50 @@ export default function replace(options = {}) { return { name: 'replace', - transform(code, id) { + renderChunk(code, chunk) { + const id = chunk.fileName; if (!keys.length) return null; if (!filter(id)) return null; + return executeReplacement(code, id); + }, - const magicString = new MagicString(code); - - let hasReplacements = false; - let match; - let start; - let end; - let replacement; - - // eslint-disable-next-line no-cond-assign - while ((match = pattern.exec(code))) { - hasReplacements = true; + transform(code, id) { + if (!keys.length) return null; + if (!filter(id)) return null; + return executeReplacement(code, id); + } + }; - start = match.index; - end = start + match[0].length; - replacement = String(functionValues[match[1]](id)); + function executeReplacement(code, id) { + const magicString = new MagicString(code); + if (!codeHasReplacements(code, id, magicString)) { + return null; + } - magicString.overwrite(start, end, replacement); - } + const result = { code: magicString.toString() }; + if (isSourceMapEnabled()) { + result.map = magicString.generateMap({ hires: true }); + } + return result; + } - if (!hasReplacements) return null; + function codeHasReplacements(code, id, magicString) { + let result = false; + let match; - const result = { code: magicString.toString() }; - if (options.sourceMap !== false && options.sourcemap !== false) - result.map = magicString.generateMap({ hires: true }); + // eslint-disable-next-line no-cond-assign + while ((match = pattern.exec(code))) { + result = true; - return result; + const start = match.index; + const end = start + match[0].length; + const replacement = String(functionValues[match[1]](id)); + magicString.overwrite(start, end, replacement); } - }; + return result; + } + + function isSourceMapEnabled() { + return options.sourceMap !== false && options.sourcemap !== false; + } } diff --git a/packages/replace/test/misc.js b/packages/replace/test/misc.js index c0e2c8a0c..d0a731340 100644 --- a/packages/replace/test/misc.js +++ b/packages/replace/test/misc.js @@ -1,9 +1,7 @@ -/* eslint-disable consistent-return, global-require, import/no-dynamic-require */ +/* eslint-disable consistent-return */ const test = require('ava'); const { rollup } = require('rollup'); -const { SourceMapConsumer } = require('source-map'); -const { getLocator } = require('locate-character'); const replace = require('../dist/rollup-plugin-replace.cjs.js'); @@ -33,24 +31,17 @@ test('does not mutate the values map properties', async (t) => { t.deepEqual(valuesMap, { ANSWER: '42' }); }); -test('generates sourcemaps', async (t) => { +test('can be configured with output plugins', async (t) => { const bundle = await rollup({ input: 'main.js', - onwarn(warning) { - throw new Error(warning.message); - }, plugins: [ - replace({ values: { ANSWER: '42' } }), { resolveId(id) { return id; }, load(importee) { if (importee === 'main.js') { - return 'import value from "other.js";\nlog(value);'; - } - if (importee === 'other.js') { - return 'export default ANSWER;'; + return 'log("environment", process.env.NODE_ENV);'; } } } @@ -58,53 +49,18 @@ test('generates sourcemaps', async (t) => { }); const { code, map } = getOutputFromGenerated( - await bundle.generate({ format: 'es', sourcemap: true }) + await bundle.generate({ + format: 'es', + sourcemap: true, + plugins: [ + replace({ + 'process.env.NODE_ENV': JSON.stringify('production'), + delimiters: ['', ''] + }) + ] + }) ); - await SourceMapConsumer.with(map, null, async (smc) => { - const locator = getLocator(code, { offsetLine: 1 }); - - let generatedLoc = locator('42'); - let loc = smc.originalPositionFor(generatedLoc); - t.snapshot(generatedLoc); - t.snapshot(loc); - - generatedLoc = locator('log'); - loc = smc.originalPositionFor(generatedLoc); - t.snapshot(generatedLoc); - t.snapshot(loc); - }); -}); - -test('does not generate sourcemaps if disabled', async (t) => { - let warned = false; - - const bundle = await rollup({ - input: 'main.js', - onwarn(warning) { - t.snapshot(warning.message); - warned = true; - }, - plugins: [ - replace({ values: { ANSWER: '42' }, sourcemap: false }), - { - resolveId(id) { - return id; - }, - - load(importee) { - if (importee === 'main.js') { - return 'import value from "other.js";\nlog(value);'; - } - if (importee === 'other.js') { - return 'export default ANSWER;'; - } - } - } - ] - }); - - t.truthy(!warned); - await bundle.generate({ format: 'es', sourcemap: true }); - t.truthy(warned); + t.is(code.trim(), 'log("environment", "production");'); + t.truthy(map); }); diff --git a/packages/replace/test/snapshots/sourcemaps.js.md b/packages/replace/test/snapshots/sourcemaps.js.md new file mode 100644 index 000000000..b7f5c2c46 --- /dev/null +++ b/packages/replace/test/snapshots/sourcemaps.js.md @@ -0,0 +1,185 @@ +# Snapshot report for `test/sourcemaps.js` + +The actual snapshot is saved in `sourcemaps.js.snap`. + +Generated by [AVA](https://ava.li). + +## generates sourcemaps when sourcemap is used as an output argument + +> Snapshot 1 + + { + character: 12, + column: 12, + line: 1, + } + +> Snapshot 2 + + { + column: 15, + line: 1, + name: null, + source: 'other.js', + } + +> Snapshot 3 + + { + character: 17, + column: 0, + line: 3, + } + +> Snapshot 4 + + { + column: 0, + line: 2, + name: null, + source: 'main.js', + } + +## generates sourcemaps if enabled in plugin + +> Snapshot 1 + + { + character: 12, + column: 12, + line: 1, + } + +> Snapshot 2 + + { + column: 15, + line: 1, + name: null, + source: 'other.js', + } + +> Snapshot 3 + + { + character: 17, + column: 0, + line: 3, + } + +> Snapshot 4 + + { + column: 0, + line: 2, + name: null, + source: 'main.js', + } + +## generates sourcemaps if enabled in plugin (camelcase) + +> Snapshot 1 + + { + character: 12, + column: 12, + line: 1, + } + +> Snapshot 2 + + { + column: 15, + line: 1, + name: null, + source: 'other.js', + } + +> Snapshot 3 + + { + character: 17, + column: 0, + line: 3, + } + +> Snapshot 4 + + { + column: 0, + line: 2, + name: null, + source: 'main.js', + } + +## generates sourcemaps by dfault + +> Snapshot 1 + + { + character: 12, + column: 12, + line: 1, + } + +> Snapshot 2 + + { + column: 15, + line: 1, + name: null, + source: 'other.js', + } + +> Snapshot 3 + + { + character: 17, + column: 0, + line: 3, + } + +> Snapshot 4 + + { + column: 0, + line: 2, + name: null, + source: 'main.js', + } + +## generates sourcemaps by default + +> Snapshot 1 + + { + character: 12, + column: 12, + line: 1, + } + +> Snapshot 2 + + { + column: 15, + line: 1, + name: null, + source: 'other.js', + } + +> Snapshot 3 + + { + character: 17, + column: 0, + line: 3, + } + +> Snapshot 4 + + { + column: 0, + line: 2, + name: null, + source: 'main.js', + } diff --git a/packages/replace/test/snapshots/sourcemaps.js.snap b/packages/replace/test/snapshots/sourcemaps.js.snap new file mode 100644 index 0000000000000000000000000000000000000000..87b75d6fe70add86dfc516ccb334f37d7c5cd156 GIT binary patch literal 435 zcmV;k0ZjfuRzVsXgJ6Aj2!08~ zzOw(OitMQ6-+PmRm+y3;APZQufdhhVI3f5F6!&m3Fl1hM5qmzB?M!5YTK9Wiw;e2C zQDtrjz7NHvJP@qU%fP_Qzz+5+BL^dk8IZOC;#eTA24Z1GHbF)<|D>$cI3B*$<3{iX*Gm#yj?74}Vd3eHeG>MKT(a|J2nnXvF d=x7ohO`@YobTo;MCQ(`^Q2?Ey^rtTl004<5zvBP^ literal 0 HcmV?d00001 diff --git a/packages/replace/test/sourcemaps.js b/packages/replace/test/sourcemaps.js new file mode 100644 index 000000000..e9c409c33 --- /dev/null +++ b/packages/replace/test/sourcemaps.js @@ -0,0 +1,127 @@ +/* eslint-disable consistent-return */ + +const test = require('ava'); +const { rollup } = require('rollup'); +const { SourceMapConsumer } = require('source-map'); +const { getLocator } = require('locate-character'); + +const replace = require('../dist/rollup-plugin-replace.cjs.js'); + +const { getOutputFromGenerated } = require('./helpers/util'); + +function verifySourcemap(code, map, t) { + return SourceMapConsumer.with(map, null, async (smc) => { + const locator = getLocator(code, { offsetLine: 1 }); + + let generatedLoc = locator('42'); + let loc = smc.originalPositionFor(generatedLoc); + t.snapshot(generatedLoc); + t.snapshot(loc); + + generatedLoc = locator('log'); + loc = smc.originalPositionFor(generatedLoc); + t.snapshot(generatedLoc); + t.snapshot(loc); + }); +} + +// Boilerplate shared configuration +const testPlugin = { + resolveId: (id) => id, + load: (importee) => { + if (importee === 'main.js') { + return 'import value from "other.js";\nlog(value);'; + } + if (importee === 'other.js') { + return 'export default ANSWER;'; + } + } +}; + +test('generates sourcemaps by default', async (t) => { + const bundle = await rollup({ + input: 'main.js', + onwarn(warning) { + throw new Error(warning.message); + }, + plugins: [replace({ values: { ANSWER: '42' } }), testPlugin] + }); + + const { code, map } = getOutputFromGenerated( + await bundle.generate({ format: 'es', sourcemap: true }) + ); + + await verifySourcemap(code, map, t); +}); + +test('generates sourcemaps if enabled in plugin', async (t) => { + const bundle = await rollup({ + input: 'main.js', + onwarn(warning) { + throw new Error(warning.message); + }, + plugins: [replace({ values: { ANSWER: '42' }, sourcemap: true }), testPlugin] + }); + + const { code, map } = getOutputFromGenerated( + await bundle.generate({ format: 'es', sourcemap: true }) + ); + + await verifySourcemap(code, map, t); +}); + +test('generates sourcemaps if enabled in plugin (camelcase)', async (t) => { + const bundle = await rollup({ + input: 'main.js', + onwarn(warning) { + throw new Error(warning.message); + }, + plugins: [replace({ values: { ANSWER: '42' }, sourceMap: true }), testPlugin] + }); + + const { code, map } = getOutputFromGenerated( + await bundle.generate({ format: 'es', sourcemap: true }) + ); + + await verifySourcemap(code, map, t); +}); + +test('does not generate sourcemaps if disabled in plugin', async (t) => { + let warned = false; + + const bundle = await rollup({ + input: 'main.js', + onwarn(warning) { + t.is( + warning.message, + "Sourcemap is likely to be incorrect: a plugin (replace) was used to transform files, but didn't generate a sourcemap for the transformation. Consult the plugin documentation for help" + ); + warned = true; + }, + plugins: [replace({ values: { ANSWER: '42' }, sourcemap: false }), testPlugin] + }); + + t.truthy(!warned); + await bundle.generate({ format: 'es', sourcemap: true }); + t.truthy(warned); +}); + +test('does not generate sourcemaps if disabled in plugin (camelcase)', async (t) => { + let warned = false; + + const bundle = await rollup({ + input: 'main.js', + onwarn(warning) { + t.is( + warning.message, + "Sourcemap is likely to be incorrect: a plugin (replace) was used to transform files, but didn't generate a sourcemap for the transformation. Consult the plugin documentation for help" + ); + warned = true; + }, + plugins: [replace({ values: { ANSWER: '42' }, sourceMap: false }), testPlugin] + }); + + t.truthy(!warned); + await bundle.generate({ format: 'es', sourcemap: true }); + t.truthy(warned); +});