Skip to content

Commit

Permalink
Merge #1632
Browse files Browse the repository at this point in the history
1632: feat(webpack): introduce experimental esbuild transpilation r=jhiode a=jhiode

To test this just add `esbuild` and `esbuild-loader` as dependencies to `hops-template-redux`.

```
$ time -l yarn hops build -p
hops-template-react:info running 'build' in 'production' mode
hops-template-react:info bundling 'node' finished after 5.6s (2.93 MB)
- server.js (2.93 MB)
hops-template-react:info bundling 'build' finished after 12.9s (318 kB)
- hops-template-react-1-71d927b348e8.js (279 kB)
- hops-template-react-4531ec959528.js (38.2 kB)
- main-e94a689b8054.css (207 B)
✨  Done in 17.02s.
```

```
$ time -l yarn hops build -p --experimentalEsbuild
hops-template-react:info running 'build' in 'production' mode
hops-template-react:info bundling 'node' finished after 4.2s (2.59 MB)
- server.js (2.59 MB)
hops-template-react:info bundling 'build' finished after 4s (223 kB)
- hops-template-react-1-89661b912d7e.js (211 kB)
- hops-template-react-202879a799d9.js (11.7 kB)
- main-e94a689b8054.css (207 B)
✨  Done in 8.63s.
```

Memory consumption also goes down from ~340MB to ~240MB.

Co-authored-by: Jonas Holland <[email protected]>
  • Loading branch information
bors[bot] and jhiode authored Mar 24, 2021
2 parents cdac922 + f8f1ad3 commit 051da1c
Show file tree
Hide file tree
Showing 50 changed files with 382 additions and 54 deletions.
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,9 @@
"babel-eslint": "^10.0.3",
"canarist": "^2.2.2",
"cz-conventional-changelog": "^3.0.2",
"esbuild": "^0.9.6",
"esbuild-jest": "^0.5.0",
"esbuild-loader": "^2.10.0",
"eslint": "^7.0.0",
"eslint-config-prettier": "^8.0.0",
"eslint-plugin-import": "^2.18.2",
Expand Down
28 changes: 20 additions & 8 deletions packages/jest-preset/jest-preset.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,22 @@ if (Number(jestMajorVersion) < 26) {
);
}

const useEsbuild = process.env.USE_EXPERIMENTAL_ESBUILD === 'true';
const jsTransform = useEsbuild
? require.resolve('./transforms/esbuild.js')
: require.resolve('./transforms/babel.js');
const tsTransform = useEsbuild
? require.resolve('./transforms/esbuild.js')
: require.resolve('ts-jest');

module.exports = {
globals: {
'ts-jest': {
babelConfig: require('./transforms/babel.js').babelConfig,
},
},
globals: useEsbuild
? {}
: {
'ts-jest': {
babelConfig: require('./transforms/babel.js').babelConfig,
},
},
moduleNameMapper: {
'^.+\\.(png|gif|jpe?g|webp|html|svg|((o|t)tf)|woff2?|ico)$': require.resolve(
'./mocks/file.js'
Expand All @@ -37,12 +47,14 @@ module.exports = {
'**/?(*.)+(spec|test).ts?(x)',
],
transform: {
'^.+\\.(js|jsx|mjs)$': require.resolve('./transforms/babel.js'),
'^.+\\.(ts|tsx)$': require.resolve('ts-jest'),
'^.+\\.(js|jsx|mjs)$': jsTransform,
'^.+\\.(ts|tsx)$': tsTransform,
'^.+\\.(gql|graphql)$': require.resolve('./transforms/graphql.js'),
},
transformIgnorePatterns: [],
setupFiles: [require.resolve('regenerator-runtime/runtime')],
setupFiles: useEsbuild
? []
: [require.resolve('regenerator-runtime/runtime')],
// fixes: https://github.com/facebook/jest/issues/6766
testURL: 'http://localhost',
};
17 changes: 17 additions & 0 deletions packages/jest-preset/transforms/esbuild.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// eslint-disable-next-line node/no-extraneous-require
const { createTransformer } = require('esbuild-jest');

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

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

return transformer.process(content, filename, config, opts);
},
};
28 changes: 20 additions & 8 deletions packages/react/build/mixin.core.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,36 @@ const { Mixin } = require('hops-mixin');

class ReactCoreMixin extends Mixin {
configureBuild(webpackConfig, { fileLoaderConfig, jsLoaderConfig }, target) {
const { experimentalEsbuild } = this.options;

webpackConfig.resolve.extensions.push('.jsx');
fileLoaderConfig.exclude.push(/\.jsx$/);
jsLoaderConfig.test.push(/\.jsx$/);

jsLoaderConfig.options.presets.push(require.resolve('@babel/preset-react'));

jsLoaderConfig.options.plugins.push(
require.resolve('@babel/plugin-transform-flow-strip-types'),
require.resolve('@babel/plugin-proposal-class-properties')
);
if (experimentalEsbuild) {
jsLoaderConfig.use[0].options.loader = 'jsx';
} else {
jsLoaderConfig.options.presets.push(
require.resolve('@babel/preset-react')
);

if (target !== 'develop' && process.env.NODE_ENV === 'production') {
jsLoaderConfig.options.plugins.push(
require.resolve('babel-plugin-transform-react-remove-prop-types')
require.resolve('@babel/plugin-transform-flow-strip-types'),
require.resolve('@babel/plugin-proposal-class-properties')
);

if (target !== 'develop' && process.env.NODE_ENV === 'production') {
jsLoaderConfig.options.plugins.push(
require.resolve('babel-plugin-transform-react-remove-prop-types')
);
}
}
}

handleArguments(argv) {
this.options = { ...this.options, ...argv };
}

diagnose({ detectDuplicatePackages }) {
detectDuplicatePackages('react', 'react-dom');
}
Expand Down
10 changes: 10 additions & 0 deletions packages/react/import-component/import-component-loader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const regex = /importComponent\s*\(\s*\(\)\s+=>\s+import\(\s*'([^']+)'\s*\)\s*\)/g;

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

module.exports = importComponentLoader;
18 changes: 14 additions & 4 deletions packages/react/import-component/mixin.core.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,20 @@ const { Mixin } = require('hops-mixin');

class ImportComponentCoreMixin extends Mixin {
configureBuild(webpackConfig, { jsLoaderConfig }) {
jsLoaderConfig.options.plugins.push([
require.resolve('../lib/babel'),
{ module: 'hops' },
]);
const { experimentalEsbuild } = this.options;

if (experimentalEsbuild) {
jsLoaderConfig.use.push(require.resolve('./import-component-loader.js'));
} else {
jsLoaderConfig.options.plugins.push([
require.resolve('../lib/babel'),
{ module: 'hops' },
]);
}
}

handleArguments(argv) {
this.options = { ...this.options, ...argv };
}
}

Expand Down
28 changes: 28 additions & 0 deletions packages/spec/integration/esbuild-typescript/__tests__/develop.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const { handleConsoleOutput } = require('../../../helpers');

describe('typescript development server', () => {
let url;

beforeAll(async () => {
const { getUrl } = HopsCLI.start('--fast-dev', '--experimental-esbuild');
url = await getUrl();
});

it('renders a simple jsx site', async () => {
const { page } = await createPage();
page.on('console', (msg) => handleConsoleOutput(msg));
await page.goto(url);
expect(await page.content()).toMatch('<h1>test</h1>');

await page.close();
});

it('supports code-splitting', async () => {
const { page } = await createPage();
page.on('console', (msg) => handleConsoleOutput(msg));
await page.goto(url);
expect(await page.content()).toMatch('<p>lorem ipsum.</p>');

await page.close();
});
});
3 changes: 3 additions & 0 deletions packages/spec/integration/esbuild-typescript/content.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import * as React from 'react';

export default () => <p>lorem ipsum.</p>;
17 changes: 17 additions & 0 deletions packages/spec/integration/esbuild-typescript/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { render, importComponent } from 'hops';
import * as React from 'react';
import { Helmet } from 'react-helmet-async';

import Headline from './headline';

const Content = importComponent(() => import('./content'));

export default render(
<>
<Helmet>
<link rel="icon" href="data:;base64,iVBORw0KGgo=" />
</Helmet>
<Headline />
<Content />
</>
);
32 changes: 32 additions & 0 deletions packages/spec/integration/esbuild-typescript/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "fixture-esbuild-typescript",
"version": "1.0.0",
"private": true,
"engines": {
"node": "12 || 14 || 15"
},
"jest": {
"displayName": "integration",
"testEnvironment": "jest-environment-hops",
"setupFilesAfterEnv": [
"../../jest.setup.js"
]
},
"hops": {
"gracePeriod": 0
},
"scripts": {
"start": "hops start --experimental-esbuild",
"build": "hops build --experimental-esbuild"
},
"dependencies": {
"esbuild": "*",
"esbuild-jest": "*",
"esbuild-loader": "*",
"hops": "*",
"hops-typescript": "*",
"react": "*",
"react-helmet-async": "*",
"typescript": "*"
}
}
3 changes: 3 additions & 0 deletions packages/spec/integration/esbuild-typescript/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "hops-typescript/tsconfig.json"
}
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import React from 'react';

const Headline = () => <h1>test</h1>;

export default Headline;
13 changes: 13 additions & 0 deletions packages/styled-components/mixin.core.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@ const { Mixin } = require('hops-mixin');

class StyledComponentsMixin extends Mixin {
configureBuild(webpackConfig, { jsLoaderConfig }, target) {
const { experimentalEsbuild } = this.options;

if (experimentalEsbuild) {
console.warn(
'The experimental esbuild transpilation does not support styled components yet!'
);
return;
}

jsLoaderConfig.options.plugins.unshift([
require.resolve('babel-plugin-styled-components'),
{
Expand All @@ -12,6 +21,10 @@ class StyledComponentsMixin extends Mixin {
},
]);
}

handleArguments(argv) {
this.options = { ...this.options, ...argv };
}
}

module.exports = StyledComponentsMixin;
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import * as React from 'react';
import { Provider } from 'react-redux';
import renderer from 'react-test-renderer';
import CounterContainer, { mapStateToProps } from '../';
Expand Down
File renamed without changes.
87 changes: 68 additions & 19 deletions packages/typescript/mixin.core.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,29 +28,78 @@ const getCompilerOptions = (ts, rootPath) => {

class TypescriptMixin extends Mixin {
configureBuild(webpackConfig, { jsLoaderConfig, allLoaderConfigs }, target) {
const { loader, options, exclude } = jsLoaderConfig;
const isDevelop =
target === 'develop' ||
(target === 'node' && process.env.NODE_ENV !== 'production');
const loaderOptions = isDevelop
? { compilerOptions: { isolatedModules: true }, transpileOnly: true }
: undefined;
const { experimentalEsbuild } = this.options;

allLoaderConfigs.unshift({
test: /\.tsx?$/,
exclude,
use: [
webpackConfig.resolve.extensions.push('.ts', '.tsx');

if (experimentalEsbuild) {
const {
include,
exclude,
use: [{ loader, options }, ...loaders],
} = jsLoaderConfig;

allLoaderConfigs.unshift(
{
loader,
options,
test: /\.ts$/,
include,
exclude,
use: [
{
loader,
options: {
...options,
loader: 'ts',
},
},
...loaders,
],
},
{
loader: require.resolve('ts-loader'),
options: loaderOptions,
},
],
});
webpackConfig.resolve.extensions.push('.ts', '.tsx');
test: /\.tsx$/,
include,
exclude,
use: [
{
loader,
options: {
...options,
loader: 'tsx',
},
},
...loaders,
],
}
);
} else {
const { exclude, loader, options } = jsLoaderConfig;

const isDevelop =
target === 'develop' ||
(target === 'node' && process.env.NODE_ENV !== 'production');
const loaderOptions = isDevelop
? { compilerOptions: { isolatedModules: true }, transpileOnly: true }
: undefined;

allLoaderConfigs.unshift({
test: /\.tsx?$/,
exclude,
use: [
{
loader,
options,
},
{
loader: require.resolve('ts-loader'),
options: loaderOptions,
},
],
});
}
}

handleArguments(argv) {
this.options = { ...this.options, ...argv };
}

diagnose() {
Expand Down
9 changes: 5 additions & 4 deletions packages/webpack/lib/plugins/log.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,11 @@ const formatWarning = (name, duration, assets, isRebuild) => {
};

const formatSuccess = (name, duration, assets, isRebuild) => {
const totalSize = Object.values(assets).reduce(
(sum, asset) => sum + asset.size,
0
);
const totalSize = Object.values(assets)
.filter((asset) => !asset.name.endsWith('.map'))
.reduce((sum, asset) => {
return sum + asset.size;
}, 0);
const message = `bundling '${name}' finished after ${duration} (${prettyBytes(
totalSize
)})`;
Expand Down
Loading

0 comments on commit 051da1c

Please sign in to comment.