Skip to content

Commit

Permalink
Allow plugins that transform code to return source maps, and compose …
Browse files Browse the repository at this point in the history
…them together. (#1048)

* Allow plugins that transform code to return source maps, and compose them together.

* Ensure map snapshots look the same no matter what directory we're running from.

* Split out V2 and V1 result handling, and tighten up types.
  • Loading branch information
pkaminski authored Oct 1, 2020
1 parent b8b2625 commit d8131c2
Show file tree
Hide file tree
Showing 11 changed files with 121 additions and 13 deletions.
1 change: 1 addition & 0 deletions snowpack/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"resolve-from": "^5.0.0",
"rimraf": "^3.0.0",
"signal-exit": "^3.0.3",
"source-map": "^0.7.3",
"strip-ansi": "^6.0.0",
"strip-comments": "^2.0.1",
"tar": "^6.0.1",
Expand Down
41 changes: 34 additions & 7 deletions snowpack/src/build/build-pipeline.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import path from 'path';
import {validatePluginLoadResult} from '../config';
import {logger} from '../logger';
import {SnowpackBuildMap, SnowpackConfig, SnowpackPlugin} from '../types/snowpack';
import {SnowpackBuildMap, SnowpackConfig, SnowpackPlugin, PluginTransformResult} from '../types/snowpack';
import {getExt, readFile, replaceExt} from '../util';
import {SourceMapConsumer, SourceMapGenerator, RawSourceMap} from 'source-map';

export interface BuildFileOptions {
isDev: boolean;
Expand Down Expand Up @@ -100,6 +101,21 @@ async function runPipelineLoadStep(
};
}

async function composeSourceMaps(id: string, base: string | RawSourceMap, derived: string | RawSourceMap) : Promise<string> {
const [baseMap, transformedMap] = await Promise.all([
new SourceMapConsumer(base),
new SourceMapConsumer(derived)
]);
try {
const generator = SourceMapGenerator.fromSourceMap(transformedMap);
generator.applySourceMap(baseMap, id);
return generator.toString();
} finally {
baseMap.destroy();
transformedMap.destroy();
}
}

/**
* Build Plugin Second Pass: If a plugin defines a
* transform() method,call it. Transform cannot change
Expand Down Expand Up @@ -139,17 +155,28 @@ async function runPipelineTransformStep(
urlPath: `./${path.basename(rootFileName + destExt)}`,
});
logger.debug(`✔ transform() success [${debugPath}]`, {name: step.name});
// if step returned a value, only update code (don’t touch .map)
if (typeof result === 'string' || Buffer.isBuffer(result)) {
// V2 API, simple string variant
output[destExt].code = result;
output[destExt].map = undefined;
} else if (result && typeof result === 'object' && (result as {result: string}).result) {
output[destExt].code = (result as {result: string}).result;
} else if (result && typeof result === 'object' && (result as PluginTransformResult).contents) {
// V2 API, structured result variant
output[destExt].code = (result as PluginTransformResult).contents;
const map = (result as PluginTransformResult).map;
let outputMap: string | undefined = undefined;
if (map && sourceMaps) { // if source maps disabled, don’t return any
if (output[destExt].map) {
outputMap = await composeSourceMaps(filePath, output[destExt].map!, map);
} else {
outputMap = typeof map === 'object' ? JSON.stringify(map) : map;
}
}
output[destExt].map = outputMap;
} else if (result && typeof result === 'object' && (result as unknown as {result: string}).result) {
// V1 API, deprecated
output[destExt].code = (result as unknown as {result: string}).result;
output[destExt].map = undefined;
}

// if source maps disabled, don’t return any
if (!sourceMaps) output[destExt].map = undefined;
}
} catch (err) {
// Attach metadata detailing where the error occurred.
Expand Down
2 changes: 1 addition & 1 deletion snowpack/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -735,7 +735,7 @@ function validatePlugin(plugin: SnowpackPlugin) {

export function validatePluginLoadResult(
plugin: SnowpackPlugin,
result: PluginLoadResult | void | undefined | null,
result: PluginLoadResult | string | void | undefined | null,
) {
const pluginName = plugin.name;
if (!result) {
Expand Down
9 changes: 6 additions & 3 deletions snowpack/src/types/snowpack.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type HttpProxy from 'http-proxy';
import type * as http from 'http';
import type {InstallOptions} from 'esinstall';
import type {RawSourceMap} from 'source-map';

export type DeepPartial<T> = {
[P in keyof T]?: T[P] extends Array<infer U>
Expand Down Expand Up @@ -51,7 +52,9 @@ export interface PluginRunOptions {
}

/** map of extensions -> code (e.g. { ".js": "[code]", ".css": "[code]" }) */
export type PluginLoadResult = string | SnowpackBuildMap;
export type PluginLoadResult = SnowpackBuildMap;

export type PluginTransformResult = {contents: string, map: string | RawSourceMap};

export interface PluginOptimizeOptions {
buildDirectory: string;
Expand All @@ -73,9 +76,9 @@ export interface SnowpackPlugin {
output: string[];
};
/** load a file that matches resolve.input */
load?(options: PluginLoadOptions): Promise<PluginLoadResult | null | undefined | void>;
load?(options: PluginLoadOptions): Promise<PluginLoadResult | string | null | undefined | void>;
/** transform a file that matches resolve.input */
transform?(options: PluginTransformOptions): Promise<string | null | undefined | void>;
transform?(options: PluginTransformOptions): Promise<PluginTransformResult | string | null | undefined | void>;
/** runs a command, unrelated to file building (e.g. TypeScript, ESLint) */
run?(options: PluginRunOptions): Promise<unknown>;
/** optimize the entire built application */
Expand Down
29 changes: 29 additions & 0 deletions test/build/__snapshots__/build.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -8137,3 +8137,32 @@ Array [
"_dist_/styles.css.proxy.js",
]
`;

exports[`snowpack build transform-sourcemap: __snowpack__/env.js 1`] = `"export default {\\"MODE\\":\\"production\\",\\"NODE_ENV\\":\\"production\\"};"`;

exports[`snowpack build transform-sourcemap: _dist_/index.js 1`] = `
"import './submodule.js';
console.log('transformed');
console.log('loaded');
//# sourceMappingURL=index.js.map"
`;

exports[`snowpack build transform-sourcemap: _dist_/index.js.map 1`] = `"{\\"version\\":3,\\"file\\":null,\\"sources\\":[\\"/home/sweet/home/snowpack/test/build/transform-sourcemap/src/index.js\\"],\\"sourcesContent\\":[\\"import './submodule.ts';console.log('loaded');\\"],\\"names\\":[],\\"mappings\\":\\"AAAA;AACA;;AACA;\\"}"`;

exports[`snowpack build transform-sourcemap: _dist_/submodule.js 1`] = `
"console.log('transformed');
console.log(\\"ts loaded\\");
//# sourceMappingURL=submodule.js.map"
`;

exports[`snowpack build transform-sourcemap: _dist_/submodule.js.map 1`] = `"{\\"version\\":3,\\"sources\\":[\\"/home/sweet/home/snowpack/test/build/transform-sourcemap/src/submodule.js\\"],\\"names\\":[],\\"mappings\\":\\";AAAA\\",\\"sourcesContent\\":[\\"console.log(\\\\\\"ts loaded\\\\\\");\\"]}"`;

exports[`snowpack build transform-sourcemap: allFiles 1`] = `
Array [
"__snowpack__/env.js",
"_dist_/index.js",
"_dist_/index.js.map",
"_dist_/submodule.js",
"_dist_/submodule.js.map",
]
`;
7 changes: 5 additions & 2 deletions test/build/build.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ const os = require('os');
const STRIP_WHITESPACE = /((\s+$)|((\\r\\n)|(\\n)))/gm;
const STRIP_REV = /\?rev=\w+/gm;
const STRIP_CHUNKHASH = /([\w\-]+\-)[a-z0-9]{8}(\.js)/g;
const STRIP_ROOTDIR = /"[^"]+(\/snowpack\/test\/)/g;

/** format diffs to be meaningful */
function format(stdout) {
return stdout
.replace(STRIP_REV, '?rev=XXXXXXXXXX')
.replace(STRIP_CHUNKHASH, '$1XXXXXXXX$2')
.replace(STRIP_WHITESPACE, '');
.replace(STRIP_WHITESPACE, '')
.replace(STRIP_ROOTDIR, '"/home/sweet/home$1');
}

describe('snowpack build', () => {
Expand Down Expand Up @@ -61,7 +63,8 @@ describe('snowpack build', () => {
entry.endsWith('.css') ||
entry.endsWith('.html') ||
entry.endsWith('.js') ||
entry.endsWith('.json')
entry.endsWith('.json') ||
entry.endsWith('.map')
) {
const f1 = readFileSync(path.resolve(actual, entry), {encoding: 'utf8'});
expect(format(f1)).toMatchSnapshot(entry.replace(/\\/g, '/'));
Expand Down
16 changes: 16 additions & 0 deletions test/build/transform-sourcemap/custom-transform-plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
const MagicString = require('magic-string');

module.exports = function () {
return {
transform: async ({id, fileExt, contents}) => {
const ms = new MagicString(contents);
ms.appendLeft(contents.indexOf('console.log'), `console.log('transformed');\n`);
const map = ms.generateMap({source: id, hires: false, includeContent: true});
return {
contents: ms.toString(),
// Try returning both object and string map formats.
map: fileExt === '.js' ? map : map.toString()
}
},
};
};
14 changes: 14 additions & 0 deletions test/build/transform-sourcemap/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"private": true,
"version": "1.0.1",
"name": "@snowpack/test-plugin-transform",
"description": "A test to make sure that the plugin transform() hook is working as expected",
"scripts": {
"start": "snowpack dev",
"testbuild": "snowpack build"
},
"devDependencies": {
"magic-string": "^0.25.7",
"snowpack": "^2.7.0"
}
}
11 changes: 11 additions & 0 deletions test/build/transform-sourcemap/snowpack.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"mount": {
"./src": "/_dist_"
},
"buildOptions": {
"clean": true,
"minify": false,
"sourceMaps": true
},
"plugins": ["./custom-transform-plugin.js"]
}
3 changes: 3 additions & 0 deletions test/build/transform-sourcemap/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import './submodule.ts';

console.log('loaded');
1 change: 1 addition & 0 deletions test/build/transform-sourcemap/src/submodule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log('ts loaded');

1 comment on commit d8131c2

@vercel
Copy link

@vercel vercel bot commented on d8131c2 Oct 1, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.