-
Notifications
You must be signed in to change notification settings - Fork 44
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
feat(webpack): introduce experimental esbuild transpilation #1632
Changes from all commits
dc59abd
1746b4d
13028d2
6a416a8
5220018
6a2845e
56ad96e
f8f1ad3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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); | ||
}, | ||
}; |
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; |
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(); | ||
}); | ||
}); |
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>; |
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 /> | ||
</> | ||
); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we also run a jest test in this integration test, to demonstrate how it works? |
||
"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": "*" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"extends": "hops-typescript/tsconfig.json" | ||
} |
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; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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!' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It doesn't support it for SSR, right? But it would still give useful output in dev mode? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I haven't really looked into it so I'm not sure yet what works and what doesn't. |
||
); | ||
return; | ||
} | ||
|
||
jsLoaderConfig.options.plugins.unshift([ | ||
require.resolve('babel-plugin-styled-components'), | ||
{ | ||
|
@@ -12,6 +21,10 @@ class StyledComponentsMixin extends Mixin { | |
}, | ||
]); | ||
} | ||
|
||
handleArguments(argv) { | ||
this.options = { ...this.options, ...argv }; | ||
} | ||
} | ||
|
||
module.exports = StyledComponentsMixin; |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -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], | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
To make it a little more clear what's going on here. |
||||||
} = 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() { | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another idea: We could do a regex test for
importComponent
and if we find it, we then load acorn / babel / whatever to do the actual AST transform. That would be safer, because a regex replace alone can't catch things likeimport { importComponent as importC } from 'hops'
or it might replace the wrong thing (if for some reason a file contains a function call toimportComponent()
from a different package.