Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
marvinhagemeister committed Oct 7, 2021
1 parent bbe841c commit 3043967
Show file tree
Hide file tree
Showing 15 changed files with 212 additions and 107 deletions.
4 changes: 2 additions & 2 deletions packages/wmr/src/lib/npm-middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ export function npmEtagCache() {
const url = new URL(req.url, 'https://localhost');
let id = path.posix.normalize(url.pathname);

if (!id.startsWith('/@npm/@id/')) {
if (!id.startsWith('/@npm/')) {
return next();
}

id = id.slice('/@npm/@id/'.length);
id = id.slice('/@npm/'.length);
if (!isValidPackageName(id)) {
return next();
}
Expand Down
25 changes: 24 additions & 1 deletion packages/wmr/src/lib/plugins.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import path from 'path';
import htmPlugin from '../plugins/htm-plugin.js';
import sucrasePlugin from '../plugins/sucrase-plugin.js';
import wmrPlugin from '../plugins/wmr/plugin.js';
Expand Down Expand Up @@ -28,6 +29,7 @@ import { lessPlugin } from '../plugins/less-plugin.js';
import { workerPlugin } from '../plugins/worker-plugin.js';
import { npmPlugin } from '../plugins/npm-plugin/index.js';
import tsConfigPathsPlugin from '../plugins/tsconfig-paths-plugin.js';
import { getNpmPlugins } from '../plugins/npm-plugin/npm-bundle.js';

/**
* @param {import("wmr").Options & { isIIFEWorker?: boolean}} options
Expand All @@ -51,6 +53,14 @@ export function getPlugins(options) {
registry
} = options;

const npmCacheDir = path.join(cwd, '.cache', '@npm');

/**
* Map of package name to folder on disk
* @type {Map<string, string>}
*/
const resolutionCache = new Map();

// Plugins are pre-sorted
let split = plugins.findIndex(p => p.enforce === 'post');
if (split === -1) split = plugins.length;
Expand Down Expand Up @@ -102,7 +112,20 @@ export function getPlugins(options) {
// Only transpile CommonJS in node_modules and explicit .cjs files:
include: /(^npm\/|[/\\]node_modules[/\\]|\.cjs$)/
}),
npmPlugin({ cwd, autoInstall, production, registryUrl: registry }),

...(production
? getNpmPlugins({
autoInstall,
production,
cacheDir: npmCacheDir,
cwd,
registryUrl: registry,
resolutionCache,
browserReplacement: new Map()
})
: []),
!production &&
npmPlugin({ cwd, cacheDir: npmCacheDir, autoInstall, production, registryUrl: registry, resolutionCache, alias }),
resolveExtensionsPlugin({
extensions: ['.ts', '.tsx', '.js', '.cjs'],
index: true
Expand Down
34 changes: 21 additions & 13 deletions packages/wmr/src/plugins/npm-plugin/commonjs.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,28 @@ export function commonjsPlugin({ production }) {

if (!hasCjsKeywords && hasEsmKeywords) return;

const result = transform(code, {
parse: this.parse,
plugins: [
replace({ 'process.env.NODE_ENV': 'development', __DEV__: !!production }),
optimize(),
commonjsToEsm()
]
});
let result;
try {
result = transform(code, {
parse: this.parse,
plugins: [
replace({ 'process.env.NODE_ENV': 'development', __DEV__: !!production }),
optimize(),
commonjsToEsm()
]
});

return {
code: result.code,
// FIXME: Sourcemap
map: null
};
if (code !== result.code) {
console.log('CJS', id, result.code);
return {
code: result.code,
map: result.map
};
}
} catch (err) {
console.log('ERR', code);
throw err;
}
}
};
}
20 changes: 8 additions & 12 deletions packages/wmr/src/plugins/npm-plugin/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@ const log = debug('npm', 196);
* @param {boolean} options.autoInstall
* @param {boolean} options.production
* @param {string} options.registryUrl
* @param {string} options.cacheDir
* @param {Record<string, string>} options.alias
* @param {Map<string, string>} options.resolutionCache
* @returns {import('rollup').Plugin}
*/
export function npmPlugin({ cwd, autoInstall, production, registryUrl }) {
export function npmPlugin({ cwd, cacheDir, autoInstall, production, registryUrl, resolutionCache, alias }) {
const PREFIX = '\0npm:';

const cacheDir = path.join(cwd, '.cache', '@npm');

/** @type {Map<string, { code: string, map: any }>} */
const chunkCache = new Map();

Expand All @@ -38,10 +39,11 @@ export function npmPlugin({ cwd, autoInstall, production, registryUrl }) {
* @param {object} options
* @param {string} options.packageName
* @param {string} options.diskCacheDir
* @param {Record<string, string>} options.alias
* @param {Map<string, string>} options.resolutionCache
* @returns {Promise<{ code: string, map: any }>}
*/
async function bundleNpmPackage(id, { packageName, diskCacheDir, resolutionCache }) {
async function bundleNpmPackage(id, { packageName, diskCacheDir, resolutionCache, alias }) {
const deferred = new Deferred();
pending.set(id, deferred);

Expand All @@ -53,7 +55,7 @@ export function npmPlugin({ cwd, autoInstall, production, registryUrl }) {
}

log(kl.dim(`bundle: `) + kl.cyan(id));
let result = await npmBundle(id, { autoInstall, production, cacheDir, cwd, resolutionCache, registryUrl });
let result = await npmBundle(id, { autoInstall, production, cacheDir, cwd, resolutionCache, registryUrl, alias });

await Promise.all(
result.output.map(async chunkOrAsset => {
Expand Down Expand Up @@ -88,12 +90,6 @@ export function npmPlugin({ cwd, autoInstall, production, registryUrl }) {
return chunk;
}

/**
* Map of package name to folder on disk
* @type {Map<string, string>}
*/
const resolutionCache = new Map();

return {
name: 'npm-plugin',
async resolveId(id) {
Expand All @@ -114,7 +110,7 @@ export function npmPlugin({ cwd, autoInstall, production, registryUrl }) {
log(kl.dim(`asset ${id}, wait for bundling `) + kl.cyan(name));
const diskCacheDir = path.join(cacheDir, escapeFilename(name));
if (!deferred) {
await bundleNpmPackage(name, { packageName: name, diskCacheDir, resolutionCache });
await bundleNpmPackage(name, { packageName: name, diskCacheDir, resolutionCache, alias });
} else {
await deferred;
}
Expand Down
63 changes: 54 additions & 9 deletions packages/wmr/src/plugins/npm-plugin/npm-bundle.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { npmAutoInstall } from './npm-auto-install.js';
import jsonPlugin from '../json-plugin.js';
import sizeWarningPlugin from './size-warning-plugin.js';
import { onWarn } from '../../lib/output-utils.js';
import aliasPlugin from '../aliases-plugin.js';

/** @type {import('rollup').WarningHandlerWithDefault} */
function customWarn(warning) {
Expand All @@ -22,6 +23,41 @@ function customWarn(warning) {
onWarn(warning);
}

/**
* @param {object} options
* @param {boolean} options.autoInstall
* @param {boolean} options.production
* @param {string} options.cacheDir
* @param {string} options.cwd
* @param {string} options.registryUrl
* @param {string} [options.requestId]
* @param {Map<string, string>} options.resolutionCache
* @param {Map<string, string>} options.browserReplacement
* @returns {import('rollup').Plugin[]}
*/
export function getNpmPlugins({
autoInstall,
production,
cacheDir,
cwd,
resolutionCache,
registryUrl,
browserReplacement,
requestId
}) {
// @ts-ignore
return [
browserFieldPlugin({ browserReplacement }),
!production && requestId && npmExternalDeps({ requestId }),
!process.env.DISABLE_LOCAL_NPM && npmLocalPackage({ root: cwd }),
autoInstall && npmAutoInstall({ cacheDir, registryUrl }),
npmLoad({ browserReplacement, resolutionCache, production }),
commonjsPlugin({ production }),
subPackageLegacy(),
sizeWarningPlugin()
].filter(Boolean);
}

/**
* @param {string} requestId
* @param {object} options
Expand All @@ -30,29 +66,38 @@ function customWarn(warning) {
* @param {string} options.cacheDir
* @param {string} options.cwd
* @param {string} options.registryUrl
* @param {Record<string, string>} options.alias
* @param {Map<string, string>} options.resolutionCache
*/
export async function npmBundle(requestId, { autoInstall, production, cacheDir, cwd, resolutionCache, registryUrl }) {
export async function npmBundle(
requestId,
{ autoInstall, production, cacheDir, cwd, resolutionCache, registryUrl, alias }
) {
const meta = getPackageInfo(requestId);
const pkgName = meta.name;

/** @type {Map<string, string>} */
const browserReplacement = new Map();

console.log('REQUEST', requestId);

const bundle = await rollup.rollup({
input: requestId,
external: [...builtinModules],
onwarn: customWarn,
plugins: [
browserFieldPlugin({ browserReplacement }),
npmExternalDeps({ requestId }),
!process.env.DISABLE_LOCAL_NPM && npmLocalPackage({ root: cwd }),
autoInstall && npmAutoInstall({ cacheDir, registryUrl }),
npmLoad({ browserReplacement, resolutionCache }),
aliasPlugin({ alias }),
jsonPlugin({ root: cwd }),
commonjsPlugin({ production }),
subPackageLegacy({ rootId: requestId }),
sizeWarningPlugin()
...getNpmPlugins({
requestId,
autoInstall,
production,
cacheDir,
cwd,
resolutionCache,
registryUrl,
browserReplacement
})
]
});

Expand Down
7 changes: 4 additions & 3 deletions packages/wmr/src/plugins/npm-plugin/npm-load.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ const log = debug('npm-load');
* @param {object} options
* @param {Map<string, string>} options.browserReplacement
* @param {Map<string, string>} options.resolutionCache
* @param {boolean} options.production
* @returns {import('rollup').Plugin}
*/
export function npmLoad({ browserReplacement, resolutionCache }) {
export function npmLoad({ browserReplacement, resolutionCache, production }) {
return {
name: 'npm-load',
async resolveId(id, importer) {
Expand Down Expand Up @@ -82,7 +83,7 @@ export function npmLoad({ browserReplacement, resolutionCache }) {
const subPkg = await readJson(path.join(modDir, pathname, 'package.json'));
entry = path.join(modDir, pathname, subPkg.module || subPkg.main || 'index.js');
} catch (err) {
entry = pathname;
entry = path.join(modDir, pathname);
}
}
}
Expand All @@ -93,7 +94,7 @@ export function npmLoad({ browserReplacement, resolutionCache }) {

// Some packages use non-js entry files, but rollup only supports js.
// So we expect other plugins to handle assets.
if (!/\.(?:[tj]sx?|[cm]js|[mc]ts)/.test(path.extname(entry))) {
if (!production && !/\.(?:[tj]sx?|[cm]js|[mc]ts)/.test(path.extname(entry))) {
return {
code: '',
map: null,
Expand Down
4 changes: 1 addition & 3 deletions packages/wmr/src/plugins/npm-plugin/sub-package-legacy.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,9 @@ import { isDirectory } from '../../lib/fs-utils.js';
/**
* Legacy way of defining package entry points before the
* "export" field in `package.json` was a thing.
* @param {object} options
* @param {string} options.rootId
* @returns {import('rollup').Plugin}
*/
export function subPackageLegacy({ rootId }) {
export function subPackageLegacy() {
return {
name: 'legacy-sub-package',
async resolveId(id, importer) {
Expand Down
9 changes: 4 additions & 5 deletions packages/wmr/src/plugins/npm-plugin/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,11 @@ export function isValidPackageName(id) {
!/node_modules|favicon\.ico/.test(id) &&
// Must not be a built-in node module
!builtins.has(id) &&
// Must be lowercase
id.toLowerCase() === id &&
// Package name must be lowercase and contain path segment
// if scoped
/^(?:@[^/A-Z]+\/[^/A-Z]+|[^/A-Z]+)/.test(id) &&
// Must not contain special characters
!/[~'!()*;,?:&=+$]/.test(id) &&
// Must contain a second path segment if scoped
((id[0] === '@' && id.indexOf('/') > 0) || true);
!/[~'!()*;,?:&=+$]/.test(id);

return isValid;
}
Expand Down
16 changes: 10 additions & 6 deletions packages/wmr/src/wmr-middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ export default function wmrMiddleware(options) {

// Workaround for transform forcing extensionless ids to be
// non-js
let hasIdPrefix = false;
let isVirtual = false;

let file = '';
let id = path;
Expand All @@ -257,14 +257,18 @@ export default function wmrMiddleware(options) {
// Path for virtual modules that refer to an unprefixed id.
if (path.startsWith('/@id/')) {
// Virtual paths have no exact file match, so we don't set `file`
hasIdPrefix = true;
isVirtual = true;
id = path.slice('/@id/'.length);

// Add back leading slash if it was part of the virtual id.
// Example: `/@windicss/windi.css`
if (req.path.startsWith('/@id//')) {
id = '/' + id;
}
} else if (path.startsWith('/@npm/')) {
// Virtual paths have no exact file match, so we don't set `file`
id = path.slice('/@npm/'.length);
isVirtual = true;
} else if (path.startsWith('/@alias/')) {
id = posix.normalize(path.slice('/@alias/'.length));

Expand All @@ -279,7 +283,7 @@ export default function wmrMiddleware(options) {

if (path.startsWith('/@id/')) {
// Virtual paths have no exact file match, so we don't set `file`
hasIdPrefix = true;
isVirtual = true;
path = path.slice('/@id'.length);
}

Expand All @@ -296,7 +300,7 @@ export default function wmrMiddleware(options) {
// Normalize the cacheKey so it matches what will be in the WRITE_CACHE, where we store in native paths
cacheKey = cacheKey.split(posix.sep).join(sep);

if (!hasIdPrefix) {
if (!isVirtual) {
id = `./${id}`;
}

Expand Down Expand Up @@ -331,7 +335,7 @@ export default function wmrMiddleware(options) {
} else if (queryParams.has('asset')) {
cacheKey += '?asset';
transform = TRANSFORMS.asset;
} else if (prefix || hasIdPrefix || isModule || /\.([mc]js|[tj]sx?)$/.test(file) || STYLE_REG.test(file)) {
} else if (prefix || isVirtual || isModule || /\.([mc]js|[tj]sx?)$/.test(file) || STYLE_REG.test(file)) {
transform = TRANSFORMS.js;
} else if (file.startsWith(root + sep) && (await isFile(file))) {
// Ignore dotfiles
Expand Down Expand Up @@ -591,7 +595,7 @@ export const TRANSFORMS = {
spec = relative(root, spec).split(sep).join(posix.sep);
}
// Retain bare specifiers when serializing to url
else if (!/^\.?\.\//.test(spec)) {
else if (!/^\.?\.\//.test(spec) && prefix !== 'npm') {
spec = `@id/${spec}`;
}

Expand Down
Loading

0 comments on commit 3043967

Please sign in to comment.