Skip to content

Commit

Permalink
Merge pull request #53 from chialab/refactor/meta-url-module-resoluti…
Browse files Browse the repository at this point in the history
…on-warning

Warn when using module resolution with meta uris
  • Loading branch information
edoardocavazza committed May 2, 2022
2 parents 4084872 + d1a4a9e commit 8f0421e
Show file tree
Hide file tree
Showing 6 changed files with 172 additions and 26 deletions.
2 changes: 1 addition & 1 deletion packages/esbuild-plugin-html/test/test.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,7 @@ body {
plugins: [
virtualPlugin([
{
path: 'index.html',
path: new URL('./index.html', import.meta.url).pathname,
contents: `<!DOCTYPE html>
<html lang="en">
<head>
Expand Down
81 changes: 60 additions & 21 deletions packages/esbuild-plugin-meta-url/lib/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import path from 'path';
import { isUrl, hasSearchParam } from '@chialab/node-resolve';
import { parse, walk, getIdentifierValue, getBlock, TokenType } from '@chialab/estransform';
import { parse, walk, getIdentifierValue, getBlock, getLocation, TokenType } from '@chialab/estransform';
import { useRna } from '@chialab/esbuild-rna';

/**
Expand Down Expand Up @@ -141,6 +141,11 @@ export default function({ emit = true } = {}) {

const { helpers, processor } = await parse(code, args.path);

/**
* @type {import('esbuild').Message[]}
*/
const warnings = [];

await walk(processor, () => {
const value = getMetaUrl(processor);
if (typeof value !== 'string' || isUrl(value)) {
Expand All @@ -158,24 +163,55 @@ export default function({ emit = true } = {}) {
}

promises.push(Promise.resolve().then(async () => {
const { path: resolvedPath } = await build.resolve(value.split('?')[0], {
kind: 'dynamic-import',
importer: args.path,
namespace: 'file',
resolveDir: path.dirname(args.path),
pluginData: null,
});

if (!resolvedPath) {
return;
const requestName = value.split('?')[0];
const candidates = [];
if (requestName.startsWith('./') || requestName.startsWith('../')) {
candidates.push(requestName);
} else {
candidates.push(`./${requestName}`, requestName);
}

const entryLoader = buildLoaders[path.extname(resolvedPath)] || 'file';
const entryPoint = emit ?
(entryLoader !== 'file' && entryLoader !== 'json' ? await emitChunk({ entryPoint: resolvedPath }) : await emitFile(resolvedPath)).path :
`./${path.relative(path.dirname(args.path), resolvedPath)}`;

helpers.overwrite(startToken.start, endToken.end, `new URL('${entryPoint}', ${baseUrl})`);
while (candidates.length) {
const pathName = /** @type {string} */ (candidates.shift());
const { path: resolvedPath } = await build.resolve(pathName, {
kind: 'dynamic-import',
importer: args.path,
namespace: 'file',
resolveDir: path.dirname(args.path),
pluginData: null,
});

if (!resolvedPath) {
continue;
}

if (!pathName.startsWith('./') && !pathName.startsWith('../')) {
const location = getLocation(code, startToken.start);
warnings.push({
pluginName: 'meta-url',
text: `Resolving '${pathName}' as module is not a standard behavior and may be removed in a future relase of the plugin.`,
location: {
file: args.path,
namespace: args.namespace,
...location,
length: endToken.end - startToken.start,
lineText: code.split('\n')[location.line - 1],
suggestion: 'Externalize module import using a JS proxy file.',
},
notes: [],
detail: '',
});
}

const entryLoader = buildLoaders[path.extname(resolvedPath)] || 'file';
const entryPoint = emit ?
(entryLoader !== 'file' && entryLoader !== 'json' ? await emitChunk({ entryPoint: resolvedPath }) : await emitFile(resolvedPath)).path :
`./${path.relative(path.dirname(args.path), resolvedPath)}`;

helpers.overwrite(startToken.start, endToken.end, `new URL('${entryPoint}', ${baseUrl})`);

break;
}
}));
});

Expand All @@ -189,10 +225,13 @@ export default function({ emit = true } = {}) {
helpers.prepend('var __currentScriptUrl__ = document.currentScript && document.currentScript.src || document.baseURI;\n');
}

return helpers.generate({
sourcemap: !!sourcemap,
sourcesContent,
});
return {
...helpers.generate({
sourcemap: !!sourcemap,
sourcesContent,
}),
warnings,
};
});
},
};
Expand Down
53 changes: 53 additions & 0 deletions packages/esbuild-plugin-meta-url/test/test.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import path from 'path';
import esbuild from 'esbuild';
import metaUrl from '@chialab/esbuild-plugin-meta-url';
import virtual from '@chialab/esbuild-plugin-virtual';
import { expect } from 'chai';

describe('esbuild-plugin-meta-url', () => {
Expand Down Expand Up @@ -173,4 +174,56 @@ var file = new URL("./file.txt?emit=file", "file://" + __filename);
expect(file.text).to.be.equal('test\n');
expect(path.dirname(result.path)).to.be.equal(path.dirname(file.path));
});

it('should resolve a module with warnings', async () => {
const { warnings, outputFiles: [result, file] } = await esbuild.build({
absWorkingDir: new URL('.', import.meta.url).pathname,
stdin: {
resolveDir: new URL('.', import.meta.url).pathname,
sourcefile: new URL(import.meta.url).pathname,
contents: 'export const file = new URL(\'npm_module\', import.meta.url);',
},
format: 'esm',
outdir: 'out',
loader: {
'.txt': 'file',
},
bundle: true,
write: false,
plugins: [
virtual([{
path: 'npm_module',
contents: 'test\n',
loader: 'js',
}]),
metaUrl(),
],
});

expect(result.text).to.be.equal(`// test.spec.js
var file = new URL("./npm_module?emit=file", import.meta.url);
export {
file
};
`);
expect(file.text).to.be.equal('test\n');
expect(path.dirname(result.path)).to.be.equal(path.dirname(file.path));
expect(warnings).to.be.deep.equal([
{
pluginName: 'meta-url',
text: 'Resolving \'npm_module\' as module is not a standard behavior and may be removed in a future relase of the plugin.',
detail: '',
notes: [],
location: {
column: 20,
file: 'test.spec.js',
length: 38,
line: 1,
lineText: 'export const file = new URL(\'npm_module\', import.meta.url);',
namespace: 'file',
suggestion: 'Externalize module import using a JS proxy file.',
},
},
]);
});
});
4 changes: 2 additions & 2 deletions packages/esbuild-plugin-virtual/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ export default function virtual(entries) {

entries.forEach((entry) => {
const resolveDir = entry.resolveDir || rootDir;
const virtualFilePath = path.join(resolveDir, entry.path);
const filter = new RegExp(escapeRegexBody(entry.path));
const virtualFilePath = path.isAbsolute(entry.path) ? entry.path : path.join(resolveDir, entry.path);
const filter = new RegExp(`^${escapeRegexBody(entry.path)}$`);
const entryFilter = new RegExp(escapeRegexBody(virtualFilePath));

build.onResolve({ filter }, () => ({
Expand Down
29 changes: 27 additions & 2 deletions packages/esbuild-rna/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export * from './helpers.js';
*/

/**
* @typedef {{ code: string, map?: import('@chialab/estransform').SourceMap|null, resolveDir?: string }} OnTransformResult
* @typedef {{ code: string, map?: import('@chialab/estransform').SourceMap|null, resolveDir?: string, errors?: import('esbuild').Message[], warnings?: import('esbuild').Message[] }} OnTransformResult
*/

/**
Expand Down Expand Up @@ -266,6 +266,8 @@ export function useRna(build) {
const { transform } = state;

const maps = [];
const warnings = [];
const errors = [];
for (const { options, callback } of transform) {
const { namespace: optionsNamespace = 'file', filter } = options;
if (namespace !== optionsNamespace) {
Expand All @@ -283,6 +285,12 @@ export function useRna(build) {
});
if (result) {
code = result.code;
if (result.warnings) {
warnings.push(...result.warnings);
}
if (result.errors) {
errors.push(...result.errors);
}
if (result.map) {
maps.push(result.map);
}
Expand All @@ -297,6 +305,8 @@ export function useRna(build) {
contents: code,
loader,
resolveDir,
warnings,
errors,
};
}

Expand All @@ -313,6 +323,8 @@ export function useRna(build) {
contents: sourceMap ? inlineSourcemap(code.toString(), sourceMap) : code,
loader,
resolveDir,
warnings,
errors,
};
},
/**
Expand All @@ -324,7 +336,20 @@ export function useRna(build) {
async emitFile(source, buffer) {
const { assetNames = '[name]' } = build.initialOptions;

buffer = buffer || await readFile(source);
if (!buffer) {
const result = await rnaBuild.load({
pluginData: null,
namespace: 'file',
suffix: '',
path: source,
});

if (result.contents) {
buffer = Buffer.from(result.contents);
} else {
buffer = await readFile(source);
}
}

const computedName = rnaBuild.computeName(assetNames, source, buffer);
const outputFile = path.join(fullOutDir, computedName);
Expand Down
29 changes: 29 additions & 0 deletions packages/estransform/lib/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,35 @@ export function getIdentifierValue(processor, id) {
}
}

/**
* Get token location.
* @param {string} code Source code.
* @param {number} index Token index.
* @return A location.
*/
export function getLocation(code, index) {
let it = 0;
let line = 1;
let column = -1;

if (index > code.length) {
throw new Error('Token index exceeds source code length');
}

while (it <= index) {
const char = code[it];
if (char === '\n') {
line++;
column = -1;
} else {
column++;
}
it++;
}

return { line, column };
}

/**
* @param {import('./parser.js').TokenProcessor} processor
* @param {TokenType} [openingToken]
Expand Down

0 comments on commit 8f0421e

Please sign in to comment.