Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix importComponent #2070

Merged
merged 10 commits into from
Feb 16, 2022
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,13 @@
},
{
"url": "enc:r6BTklZr+axySHpOBj0JysOusV4S8o7YtSxZ9fi66rL5grn3vMkHUbMdb3OY5+tv",
"directory": "internal-hops"
"directory": "internal-hops",
"branch": "wait-for-compilation"
}
],
"rootManifest": {
"resolutions": {
"@babel/core": "7.15.0",
"colors": "1.4.0",
"graphql-tag": "^2.12"
}
Expand Down Expand Up @@ -250,7 +252,7 @@
"@commitlint/cli": "^13.0.0",
"@commitlint/config-conventional": "^13.0.0",
"@commitlint/config-lerna-scopes": "^13.0.0",
"canarist": "^2.3.1",
"canarist": "^2.3.2",
"cz-conventional-changelog": "^3.3.0",
"eslint": "^8.0.0",
"eslint-config-prettier": "^8.3.0",
Expand Down
23 changes: 22 additions & 1 deletion packages/jest-environment/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ const build = ({ cwd, env = {}, argv = [] }) =>
const startServer = ({ cwd, command, env = {}, argv = [] }) => {
const teardownPromise = new EProm();
const urlPromise = new EProm();
const hasFinishedPromise = new EProm();

let stdout = '';
let stderr = '';

Expand All @@ -61,6 +63,12 @@ const startServer = ({ cwd, command, env = {}, argv = [] }) => {
return teardownPromise;
};

let linesToFinish = [];
const hasFinished = (lines) => {
linesToFinish.push(...lines);
return hasFinishedPromise;
};

started.stdout.on('data', (data) => {
const line = data.toString('utf-8');
debug('stdout >', line);
Expand All @@ -71,6 +79,19 @@ const startServer = ({ cwd, command, env = {}, argv = [] }) => {
debug('found match:', url);
urlPromise.resolve(url);
}

if (
linesToFinish.length > 0 &&
linesToFinish.every((lineToFinish) => stdout.includes(lineToFinish))
) {
hasFinishedPromise.resolve();
}

if (line.match(/bundling [^ ]+ failed/)) {
setTimeout(() => {
hasFinishedPromise.reject({ stdout, stderr });
}, 5000);
}
});

started.stderr.on('data', (data) => {
Expand All @@ -91,7 +112,7 @@ const startServer = ({ cwd, command, env = {}, argv = [] }) => {
stopServer();
});

return { getUrl: () => urlPromise, stopServer };
return { getUrl: () => urlPromise, stopServer, hasFinished };
};

const isPuppeteerDisabled = (pragmas) => {
Expand Down
8 changes: 6 additions & 2 deletions packages/jest-environment/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,11 @@ class FixtureEnvironment extends NodeEnvironment {
);
}
const { env, argv } = getHopsCommandModifications(args);
const { getUrl, stopServer: killServer } = startServer({
const {
getUrl,
stopServer: killServer,
hasFinished,
} = startServer({
cwd: that.cwd,
command: 'start',
env,
Expand All @@ -124,7 +128,7 @@ class FixtureEnvironment extends NodeEnvironment {
delete that.killServer;
return killServer();
};
return { getUrl, stopServer };
return { getUrl, stopServer, hasFinished };
},
};
}
Expand Down
1 change: 1 addition & 0 deletions packages/jest-preset/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"dependencies": {
"@babel/core": "^7.9.0",
"@babel/plugin-proposal-class-properties": "^7.8.3",
"@babel/plugin-syntax-jsx": "^7.8.3",
"@babel/plugin-transform-flow-strip-types": "^7.9.0",
"@babel/preset-env": "^7.9.5",
"@babel/preset-react": "^7.9.4",
Expand Down
20 changes: 15 additions & 5 deletions packages/jest-preset/transforms/esbuild.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
// eslint-disable-next-line node/no-extraneous-require
const { createTransformer } = require('esbuild-jest');
const { transformSync } = require('@babel/core');

const transformer = createTransformer({
sourcemap: true,
});

const regex = /importComponent/g;

module.exports = {
process(content, filename, config, opts) {
content = content.replace(
/importComponent\s*\(\s*\(\)\s+=>\s+import\(\s*'([^']+)'\s*\)\s*\)/g,
"importComponent({ component: require('$1') })"
);
if (!regex.test(content)) {
return transformer.process(content, filename, config, opts);
}

const result = transformSync(content, {
plugins: [
require.resolve('../helpers/babel-plugin-import-component'),
require.resolve('@babel/plugin-syntax-jsx'),
],
filename,
});

return transformer.process(content, filename, config, opts);
return transformer.process(result.code, filename, config, opts);
},
};
73 changes: 68 additions & 5 deletions packages/react/import-component/babel.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,49 @@
'use strict';

const nodePath = require('path');
const crypto = require('crypto');

/**
* This plugin transforms the following function call:
* ```javascript
* importComponent(() => import('./component'));
* ```
*
* Into the following:
*
* ```javascript
* importComponent({
* load: () => import('./component'),
* moduleId: require.resolveWeak('./component'),
* chunkName: () => 'hash'
* })
* ```
*/

const regex = /webpackChunkName:\s*(?:(['"])([^'"]*)\1|([^\s]*))/;

function extractChunkName(t, leadingComments) {
if (!leadingComments || !Array.isArray(leadingComments)) {
return;
}

const comment = leadingComments.find((comment) =>
comment.value.includes('webpackChunkName')
);

if (!comment) {
return;
}

const results = comment.value.match(regex);
const cleanChunkName = results[2] || results[3];

return cleanChunkName;
}

module.exports = ({ types: t }) => ({
visitor: {
ImportDeclaration(path) {
ImportDeclaration(path, state) {
const modules = ['hops-react', this.opts.module];
const source = path.node.source.value;
if (!modules.includes(source)) return;
Expand Down Expand Up @@ -31,19 +72,37 @@ module.exports = ({ types: t }) => ({
t.assertCallExpression(argument.get('body'));
t.assertImport(argument.get('body.callee'));

const { node } = argument.get('body.arguments.0');
const importedComponent = node.value;
const leadingComments = node.leadingComments;
const argPath = argument.get('body.arguments.0');
const importedComponent = argPath.node.value;
const resourcePathNode = t.stringLiteral(importedComponent);

let chunkName = extractChunkName(t, argPath.node.leadingComments);
if (!chunkName) {
const hasher = crypto.createHash('md4');
hasher.update(nodePath.relative(this.opts.rootDir, state.filename));
hasher.update(importedComponent);
const hash = hasher.digest('base64').slice(0, 4);

t.addComment(
argPath.node,
'leading',
` webpackChunkName: '${hash}' `
);
chunkName = hash;
}

argument.replaceWith(
t.objectExpression([
t.objectProperty(
t.identifier('load'),
t.arrowFunctionExpression(
[],
t.callExpression(t.identifier('import'), [
t.addComments(resourcePathNode, 'leading', leadingComments),
t.addComments(
resourcePathNode,
'leading',
argPath.node.leadingComments
),
])
)
),
Expand All @@ -57,6 +116,10 @@ module.exports = ({ types: t }) => ({
[resourcePathNode]
)
),
t.objectProperty(
t.identifier('chunkName'),
t.arrowFunctionExpression([], t.stringLiteral(chunkName))
),
])
);
});
Expand Down
31 changes: 26 additions & 5 deletions packages/react/import-component/import-component-loader.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,31 @@
const regex =
/importComponent\s*\(\s*\(\)\s+=>\s+import\(\s*'([^']+)'\s*\)\s*\)/g;
const { transform } = require('@babel/core');
const regex = /importComponent/g;

function importComponentLoader(source) {
return source.replace(
regex,
"importComponent({ load: () => import('$1'), moduleId: require.resolveWeak('$1') })"
const callback = this.async();

if (!regex.test(source)) {
return callback(null, source);
}

const options = this.getOptions();

transform(
source,
{
plugins: [
[require.resolve('./babel'), options],
require.resolve('@babel/plugin-syntax-jsx'),
],
filename: this.resourcePath,
},
(err, result) => {
if (err) {
return callback(err);
}

callback(null, result.code, result.map);
}
);
}

Expand Down
14 changes: 6 additions & 8 deletions packages/react/import-component/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import PropTypes from 'prop-types';
import ImportComponentContext from './context';

export const importComponent = (
{ load, moduleId },
{ load, moduleId, chunkName },
resolve = (module) => module.default
) => {
class Importer extends Component {
constructor({ hasModules }) {
constructor() {
super();
if (hasModules || __webpack_modules__[moduleId]) {
if (__webpack_modules__[moduleId]) {
this.state = { Component: resolve(__webpack_require__(moduleId)) };
} else {
this.state = { loading: true };
Expand Down Expand Up @@ -57,21 +57,19 @@ export const importComponent = (
}

Importer.propTypes = {
hasModules: PropTypes.bool,
ownProps: PropTypes.object.isRequired,
loader: PropTypes.func,
render: PropTypes.func,
};

function Import({ loader, render, ...ownProps }) {
const modules = useContext(ImportComponentContext);
const chunks = useContext(ImportComponentContext);

if (modules) {
modules.push(moduleId);
if (chunks) {
chunks.push(chunkName());
}

return createElement(Importer, {
hasModules: Boolean(modules && modules.length > 0),
loader,
render,
ownProps,
Expand Down
7 changes: 5 additions & 2 deletions packages/react/import-component/mixin.core.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ class ImportComponentCoreMixin extends Mixin {
const { experimentalEsbuild } = this.options;

if (experimentalEsbuild) {
jsLoaderConfig.use.push(require.resolve('./import-component-loader.js'));
jsLoaderConfig.use.push({
loader: require.resolve('./import-component-loader.js'),
options: { module: 'hops', rootDir: this.config.rootDir },
});
} else {
jsLoaderConfig.options.plugins.push([
require.resolve('../lib/babel'),
{ module: 'hops' },
{ module: 'hops', rootDir: this.config.rootDir },
]);
}
}
Expand Down
4 changes: 2 additions & 2 deletions packages/react/import-component/mixin.runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import { Provider } from './context';
class ImportComponentMixin extends Mixin {
bootstrap(_req, res) {
if (res) {
res.locals.modules = this.modules = [];
res.locals.chunks = this.chunks = [];
}
}

enhanceElement(element) {
return createElement(Provider, { value: this.modules }, element);
return createElement(Provider, { value: this.chunks }, element);
}
}

Expand Down
18 changes: 11 additions & 7 deletions packages/react/lib/assets.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
'use strict';

import { extname } from 'path';
import { ensureLeadingSlash } from 'pathifist';

export default (stats, modules) => {
const { entryFiles, vendorFiles, moduleFileMap } = stats;
const moduleFiles = modules.reduce(
(result, module) => [...result, ...moduleFileMap[module]],
[]
);
export default (stats, chunks) => {
const { entryFiles, vendorFiles } = stats;
const chunkFiles = chunks.reduce((result, chunk) => {
const chunkGroup = stats.namedChunkGroups[chunk];
const assets = chunkGroup.assets.map((asset) =>
ensureLeadingSlash(asset.name)
);
return [...result, ...assets];
}, []);

return [...vendorFiles, ...moduleFiles, ...entryFiles]
return [...vendorFiles, ...chunkFiles, ...entryFiles]
.filter(
(asset, index, self) =>
self.indexOf(asset) === index &&
Expand Down
1 change: 1 addition & 0 deletions packages/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"dependencies": {
"@babel/core": "^7.9.0",
"@babel/plugin-proposal-class-properties": "^7.8.3",
"@babel/plugin-syntax-jsx": "^7.8.3",
"@babel/plugin-transform-flow-strip-types": "^7.9.0",
"@babel/preset-react": "^7.9.4",
"@pmmmwh/react-refresh-webpack-plugin": "^0.4.3",
Expand Down
Loading