mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-27 16:12:12 +00:00
feat(rspack): initial commit (poc)
This commit is contained in:
parent
1c323a8810
commit
df5162d694
13
packages/nuxt/src/app/components/nuxt-root-test.vue
Normal file
13
packages/nuxt/src/app/components/nuxt-root-test.vue
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<template>
|
||||||
|
<div>Nuxt 3 ❤️ rspack</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
console.log('hey there!')
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- <style>
|
||||||
|
:root {
|
||||||
|
color: blue;
|
||||||
|
}
|
||||||
|
</style> -->
|
@ -1,4 +1,3 @@
|
|||||||
// We set __webpack_public_path via this import with webpack builder
|
|
||||||
import { createSSRApp, createApp, nextTick } from 'vue'
|
import { createSSRApp, createApp, nextTick } from 'vue'
|
||||||
import { $fetch } from 'ofetch'
|
import { $fetch } from 'ofetch'
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -9,7 +8,8 @@ import '#build/css'
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import _plugins from '#build/plugins'
|
import _plugins from '#build/plugins'
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import RootComponent from '#build/root-component.mjs'
|
// import RootComponent from '#build/root-component.mjs'
|
||||||
|
import RootComponent from './components/nuxt-root-test.vue'
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { appRootId } from '#build/nuxt.config.mjs'
|
import { appRootId } from '#build/nuxt.config.mjs'
|
||||||
|
|
||||||
|
@ -47,7 +47,8 @@ export async function generateApp (nuxt: Nuxt, app: NuxtApp, options: { filter?:
|
|||||||
nuxt.vfs[fullPath.replace(/\//g, '\\')] = contents
|
nuxt.vfs[fullPath.replace(/\//g, '\\')] = contents
|
||||||
}
|
}
|
||||||
|
|
||||||
if (template.write) {
|
// TODO: remove when we have support for virtual modules in rspack
|
||||||
|
if (template.write || nuxt.options.builder === '@nuxt/rspack-builder') {
|
||||||
await fsp.mkdir(dirname(fullPath), { recursive: true })
|
await fsp.mkdir(dirname(fullPath), { recursive: true })
|
||||||
await fsp.writeFile(fullPath, contents, 'utf8')
|
await fsp.writeFile(fullPath, contents, 'utf8')
|
||||||
}
|
}
|
||||||
|
23
packages/rspack/build.config.ts
Normal file
23
packages/rspack/build.config.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { defineBuildConfig } from 'unbuild'
|
||||||
|
|
||||||
|
export default defineBuildConfig({
|
||||||
|
declaration: true,
|
||||||
|
entries: [
|
||||||
|
'src/index'
|
||||||
|
],
|
||||||
|
dependencies: [
|
||||||
|
'@nuxt/kit',
|
||||||
|
'unplugin',
|
||||||
|
'webpack-virtual-modules',
|
||||||
|
'postcss',
|
||||||
|
'postcss-loader',
|
||||||
|
'vue-loader',
|
||||||
|
'style-resources-loader',
|
||||||
|
'url-loader',
|
||||||
|
'vue'
|
||||||
|
],
|
||||||
|
externals: [
|
||||||
|
'@nuxt/schema',
|
||||||
|
'h3'
|
||||||
|
]
|
||||||
|
})
|
76
packages/rspack/package.json
Normal file
76
packages/rspack/package.json
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
{
|
||||||
|
"name": "@nuxt/rspack-builder",
|
||||||
|
"version": "3.3.1",
|
||||||
|
"repository": "nuxt/nuxt",
|
||||||
|
"license": "MIT",
|
||||||
|
"type": "module",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"exports": {
|
||||||
|
".": "./dist/index.mjs",
|
||||||
|
"./dist/*": "./dist/*"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"prepack": "unbuild"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/core": "^7.21.0",
|
||||||
|
"@nuxt/friendly-errors-webpack-plugin": "^2.5.2",
|
||||||
|
"@nuxt/kit": "3.3.1",
|
||||||
|
"@rspack/core": "^0.1.1",
|
||||||
|
"@rspack/dev-client": "^0.1.1",
|
||||||
|
"@rspack/dev-middleware": "^0.1.1",
|
||||||
|
"@rspack/dev-server": "0.1.1",
|
||||||
|
"@rspack/postcss-loader": "^0.1.1",
|
||||||
|
"autoprefixer": "^10.4.14",
|
||||||
|
"css-minimizer-webpack-plugin": "^4.2.2",
|
||||||
|
"cssnano": "^5.1.15",
|
||||||
|
"esbuild-loader": "^3.0.1",
|
||||||
|
"escape-string-regexp": "^5.0.0",
|
||||||
|
"estree-walker": "^3.0.3",
|
||||||
|
"fork-ts-checker-webpack-plugin": "^8.0.0",
|
||||||
|
"fs-extra": "^11.1.0",
|
||||||
|
"hash-sum": "^2.0.0",
|
||||||
|
"lodash-es": "^4.17.21",
|
||||||
|
"magic-string": "^0.30.0",
|
||||||
|
"memfs": "^3.4.13",
|
||||||
|
"mini-css-extract-plugin": "^2.7.3",
|
||||||
|
"mlly": "^1.2.0",
|
||||||
|
"ohash": "^1.0.0",
|
||||||
|
"pathe": "^1.1.0",
|
||||||
|
"pify": "^6.1.0",
|
||||||
|
"postcss": "^8.4.21",
|
||||||
|
"postcss-import": "^15.1.0",
|
||||||
|
"postcss-loader": "^7.0.2",
|
||||||
|
"postcss-url": "^10.1.3",
|
||||||
|
"style-resources-loader": "^1.5.0",
|
||||||
|
"time-fix-plugin": "^2.0.7",
|
||||||
|
"ufo": "^1.1.1",
|
||||||
|
"unplugin": "^1.3.0",
|
||||||
|
"url-loader": "^4.1.1",
|
||||||
|
"vue-bundle-renderer": "^1.0.2",
|
||||||
|
"vue-loader": "^17.0.1",
|
||||||
|
"webpack-bundle-analyzer": "^4.8.0",
|
||||||
|
"webpack-hot-middleware": "^2.25.3",
|
||||||
|
"webpack-virtual-modules": "^0.5.0",
|
||||||
|
"webpackbar": "^5.0.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@nuxt/schema": "3.3.1",
|
||||||
|
"@types/lodash-es": "^4.17.6",
|
||||||
|
"@types/pify": "^5.0.1",
|
||||||
|
"@types/webpack-bundle-analyzer": "^4.6.0",
|
||||||
|
"@types/webpack-hot-middleware": "^2.25.6",
|
||||||
|
"@types/webpack-virtual-modules": "^0",
|
||||||
|
"unbuild": "latest",
|
||||||
|
"vue": "3.2.47"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vue": "^3.2.47"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^14.18.0 || ^16.10.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
}
|
112
packages/rspack/src/configs/client.ts
Normal file
112
packages/rspack/src/configs/client.ts
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import querystring from 'node:querystring'
|
||||||
|
import { resolve } from 'pathe'
|
||||||
|
import webpack from '@rspack/core'
|
||||||
|
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'
|
||||||
|
import { logger } from '@nuxt/kit'
|
||||||
|
import { joinURL } from 'ufo'
|
||||||
|
import ForkTSCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin'
|
||||||
|
|
||||||
|
import type { RspackConfigContext } from '../utils/config'
|
||||||
|
import { applyPresets } from '../utils/config'
|
||||||
|
import { nuxt } from '../presets/nuxt'
|
||||||
|
|
||||||
|
export function client (ctx: RspackConfigContext) {
|
||||||
|
ctx.name = 'client'
|
||||||
|
ctx.isClient = true
|
||||||
|
|
||||||
|
applyPresets(ctx, [
|
||||||
|
nuxt,
|
||||||
|
clientPlugins,
|
||||||
|
clientOptimization
|
||||||
|
// clientDevtool,
|
||||||
|
// clientPerformance,
|
||||||
|
// clientHMR
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
function clientDevtool (ctx: RspackConfigContext) {
|
||||||
|
if (!ctx.nuxt.options.sourcemap.client) {
|
||||||
|
ctx.config.devtool = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ctx.isDev) {
|
||||||
|
ctx.config.devtool = 'source-map'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.config.devtool = 'eval-cheap-module-source-map'
|
||||||
|
}
|
||||||
|
|
||||||
|
function clientPerformance (ctx: RspackConfigContext) {
|
||||||
|
ctx.config.performance = {
|
||||||
|
maxEntrypointSize: 1000 * 1024,
|
||||||
|
hints: ctx.isDev ? false : 'warning',
|
||||||
|
...ctx.config.performance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clientHMR (ctx: RspackConfigContext) {
|
||||||
|
const { options, config } = ctx
|
||||||
|
|
||||||
|
if (!ctx.isDev) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const clientOptions = options.webpack.hotMiddleware?.client || {}
|
||||||
|
const hotMiddlewareClientOptions = {
|
||||||
|
reload: true,
|
||||||
|
timeout: 30000,
|
||||||
|
path: joinURL(options.app.baseURL, '__webpack_hmr', ctx.name),
|
||||||
|
...clientOptions,
|
||||||
|
ansiColors: JSON.stringify(clientOptions.ansiColors || {}),
|
||||||
|
overlayStyles: JSON.stringify(clientOptions.overlayStyles || {}),
|
||||||
|
name: ctx.name
|
||||||
|
}
|
||||||
|
const hotMiddlewareClientOptionsStr = querystring.stringify(hotMiddlewareClientOptions)
|
||||||
|
|
||||||
|
// Add HMR support
|
||||||
|
const app = (config.entry as any).app as any
|
||||||
|
app.unshift(
|
||||||
|
// https://github.com/glenjamin/webpack-hot-middleware#config
|
||||||
|
`webpack-hot-middleware/client?${hotMiddlewareClientOptionsStr}`
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: HMR
|
||||||
|
// config.plugins = config.plugins || []
|
||||||
|
// config.plugins.push(new webpack.HotModuleReplacementPlugin())
|
||||||
|
}
|
||||||
|
|
||||||
|
function clientOptimization (_ctx: RspackConfigContext) {
|
||||||
|
// TODO: Improve optimization.splitChunks.cacheGroups
|
||||||
|
}
|
||||||
|
|
||||||
|
function clientPlugins (ctx: RspackConfigContext) {
|
||||||
|
const { options, config } = ctx
|
||||||
|
|
||||||
|
// webpack Bundle Analyzer
|
||||||
|
// https://github.com/webpack-contrib/webpack-bundle-analyzer
|
||||||
|
if (!ctx.isDev && ctx.name === 'client' && options.webpack.analyze) {
|
||||||
|
const statsDir = resolve(options.buildDir, 'stats')
|
||||||
|
|
||||||
|
config.plugins!.push(new BundleAnalyzerPlugin({
|
||||||
|
analyzerMode: 'static',
|
||||||
|
defaultSizes: 'gzip',
|
||||||
|
generateStatsFile: true,
|
||||||
|
openAnalyzer: true,
|
||||||
|
reportFilename: resolve(statsDir, `${ctx.name}.html`),
|
||||||
|
statsFilename: resolve(statsDir, `${ctx.name}.json`),
|
||||||
|
...options.webpack.analyze === true ? {} : options.webpack.analyze
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normally type checking runs in server config, but in `ssr: false` there is
|
||||||
|
// no server build, so we inject here instead.
|
||||||
|
if (!ctx.nuxt.options.ssr) {
|
||||||
|
if (ctx.nuxt.options.typescript.typeCheck === true || (ctx.nuxt.options.typescript.typeCheck === 'build' && !ctx.nuxt.options.dev)) {
|
||||||
|
config.plugins!.push(new ForkTSCheckerWebpackPlugin({
|
||||||
|
logger
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
2
packages/rspack/src/configs/index.ts
Normal file
2
packages/rspack/src/configs/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export { client } from './client'
|
||||||
|
export { server } from './server'
|
98
packages/rspack/src/configs/server.ts
Normal file
98
packages/rspack/src/configs/server.ts
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
import { isAbsolute } from 'pathe'
|
||||||
|
import webpack from '@rspack/core'
|
||||||
|
import ForkTSCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin'
|
||||||
|
import { logger } from '@nuxt/kit'
|
||||||
|
import type { RspackConfigContext } from '../utils/config'
|
||||||
|
import { applyPresets, getRspackConfig } from '../utils/config'
|
||||||
|
import { nuxt } from '../presets/nuxt'
|
||||||
|
import { node } from '../presets/node'
|
||||||
|
|
||||||
|
const assetPattern = /\.(css|s[ca]ss|png|jpe?g|gif|svg|woff2?|eot|ttf|otf|webp|webm|mp4|ogv)(\?.*)?$/i
|
||||||
|
|
||||||
|
export function server (ctx: RspackConfigContext) {
|
||||||
|
ctx.name = 'server'
|
||||||
|
ctx.isServer = true
|
||||||
|
|
||||||
|
applyPresets(ctx, [
|
||||||
|
nuxt,
|
||||||
|
node,
|
||||||
|
serverStandalone,
|
||||||
|
serverPreset,
|
||||||
|
serverPlugins
|
||||||
|
])
|
||||||
|
|
||||||
|
return getRspackConfig(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
function serverPreset (ctx: RspackConfigContext) {
|
||||||
|
const { config } = ctx
|
||||||
|
|
||||||
|
config.output!.filename = 'server.mjs'
|
||||||
|
|
||||||
|
config.devtool = ctx.nuxt.options.sourcemap.server ? ctx.isDev ? 'cheap-module-source-map' : 'source-map' : false
|
||||||
|
|
||||||
|
config.optimization = {
|
||||||
|
splitChunks: false,
|
||||||
|
minimize: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function serverStandalone (ctx: RspackConfigContext) {
|
||||||
|
// TODO: Refactor this out of webpack
|
||||||
|
const inline = [
|
||||||
|
'src/',
|
||||||
|
'#app',
|
||||||
|
'nuxt',
|
||||||
|
'nuxt3',
|
||||||
|
'!',
|
||||||
|
'-!',
|
||||||
|
'~',
|
||||||
|
'@/',
|
||||||
|
'#',
|
||||||
|
...ctx.options.build.transpile
|
||||||
|
]
|
||||||
|
const external = ['#internal/nitro']
|
||||||
|
|
||||||
|
if (!Array.isArray(ctx.config.externals)) { return }
|
||||||
|
ctx.config.externals.push(({ request }, cb) => {
|
||||||
|
if (!request) {
|
||||||
|
return cb(undefined, false)
|
||||||
|
}
|
||||||
|
if (external.includes(request)) {
|
||||||
|
return cb(undefined, true)
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
request[0] === '.' ||
|
||||||
|
isAbsolute(request) ||
|
||||||
|
inline.find(prefix => typeof prefix === 'string' && request.startsWith(prefix)) ||
|
||||||
|
assetPattern.test(request)
|
||||||
|
) {
|
||||||
|
// console.log('Inline', request)
|
||||||
|
return cb(undefined, false)
|
||||||
|
}
|
||||||
|
// console.log('Ext', request)
|
||||||
|
return cb(undefined, true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function serverPlugins (ctx: RspackConfigContext) {
|
||||||
|
const { config, options } = ctx
|
||||||
|
|
||||||
|
config.plugins = config.plugins || []
|
||||||
|
|
||||||
|
// Server polyfills
|
||||||
|
// TODO:
|
||||||
|
// if (options.webpack.serverURLPolyfill) {
|
||||||
|
// config.plugins.push(new webpack.ProvidePlugin({
|
||||||
|
// URL: [options.webpack.serverURLPolyfill, 'URL'],
|
||||||
|
// URLSearchParams: [options.webpack.serverURLPolyfill, 'URLSearchParams']
|
||||||
|
// }))
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Add type-checking
|
||||||
|
if (ctx.nuxt.options.typescript.typeCheck === true || (ctx.nuxt.options.typescript.typeCheck === 'build' && !ctx.nuxt.options.dev)) {
|
||||||
|
config.plugins!.push(new ForkTSCheckerWebpackPlugin({
|
||||||
|
logger
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
1
packages/rspack/src/index.ts
Normal file
1
packages/rspack/src/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './rspack'
|
61
packages/rspack/src/loaders/vue.cjs
Normal file
61
packages/rspack/src/loaders/vue.cjs
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
const util = require('node:util')
|
||||||
|
const { compileTemplate, compileScript, parse } = require('@vue/compiler-sfc')
|
||||||
|
const qs = require('qs')
|
||||||
|
// const type { LoaderContext } = require('@rspack/core')
|
||||||
|
|
||||||
|
const descCache = new Map()
|
||||||
|
|
||||||
|
module.exports = function vueLoader (content) {
|
||||||
|
const query = qs.parse(this.resourceQuery, { ignoreQueryPrefix: true })
|
||||||
|
|
||||||
|
const callback = this.async()
|
||||||
|
const inner = async () => {
|
||||||
|
if (query.vue === 'true') {
|
||||||
|
const cache = descCache.get(this.resourcePath)
|
||||||
|
const { script, styles, template } = cache
|
||||||
|
if (query.type === 'script') {
|
||||||
|
return script.content
|
||||||
|
} else if (query.type === 'style') {
|
||||||
|
const lang = styles[0]?.lang
|
||||||
|
return styles[0]?.content || 'export default {}'
|
||||||
|
} else if (query.type === 'template') {
|
||||||
|
if (!template) {
|
||||||
|
return ''
|
||||||
|
} else {
|
||||||
|
const result = compileTemplate({
|
||||||
|
source: template.content,
|
||||||
|
id: '1234'
|
||||||
|
})
|
||||||
|
return result.code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const parsed = parse(content, {
|
||||||
|
sourceMap: false,
|
||||||
|
filename: this.resourcePath
|
||||||
|
})
|
||||||
|
const descriptor = parsed.descriptor
|
||||||
|
const { script, scriptSetup, styles, template, customBlocks } = descriptor
|
||||||
|
descCache.set(this.resource, {
|
||||||
|
...parsed.descriptor,
|
||||||
|
script: compileScript(descriptor, {
|
||||||
|
id: Math.random().toString(),
|
||||||
|
isProd: false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
const jsPath = this.resourcePath + '?vue=true&type=script'
|
||||||
|
// const jscode = await util.promisify(this.loadModule)(jsPath);
|
||||||
|
const cssPath = this.resourcePath + '?vue=true&type=style'
|
||||||
|
const templatePath = this.resourcePath + '?vue=true&type=template'
|
||||||
|
// const csscode = await util.promisify(this.loadModule)(cssPath);
|
||||||
|
// console.log('csscode:', csscode);
|
||||||
|
return `import obj from ${JSON.stringify(jsPath)};
|
||||||
|
require(${
|
||||||
|
JSON.stringify(cssPath)});const { render } = require(${
|
||||||
|
JSON.stringify(templatePath)});export default { ...obj, render}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return util.callbackify(inner)((err, data) => {
|
||||||
|
callback(err, data)
|
||||||
|
})
|
||||||
|
}
|
28
packages/rspack/src/plugins/chunk.ts
Normal file
28
packages/rspack/src/plugins/chunk.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import type { Compiler } from '@rspack/core'
|
||||||
|
import webpack from '@rspack/core'
|
||||||
|
|
||||||
|
const pluginName = 'ChunkErrorPlugin'
|
||||||
|
|
||||||
|
const script = `
|
||||||
|
if (typeof ${webpack.RuntimeGlobals.require} !== "undefined") {
|
||||||
|
var _ensureChunk = ${webpack.RuntimeGlobals.ensureChunk};
|
||||||
|
${webpack.RuntimeGlobals.ensureChunk} = function (chunkId) {
|
||||||
|
return Promise.resolve(_ensureChunk(chunkId)).catch(err => {
|
||||||
|
const e = new Event("nuxt.preloadError");
|
||||||
|
e.payload = err;
|
||||||
|
window.dispatchEvent(e);
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};`
|
||||||
|
|
||||||
|
export class ChunkErrorPlugin {
|
||||||
|
apply (compiler: Compiler) {
|
||||||
|
compiler.hooks.thisCompilation.tap(pluginName, compilation =>
|
||||||
|
compilation.mainTemplate.hooks.localVars.tap(
|
||||||
|
{ name: pluginName, stage: 1 },
|
||||||
|
source => source + script
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
33
packages/rspack/src/plugins/dynamic-base.ts
Normal file
33
packages/rspack/src/plugins/dynamic-base.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { createUnplugin } from 'unplugin'
|
||||||
|
import MagicString from 'magic-string'
|
||||||
|
|
||||||
|
interface DynamicBasePluginOptions {
|
||||||
|
globalPublicPath?: string
|
||||||
|
sourcemap?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaults: DynamicBasePluginOptions = {
|
||||||
|
globalPublicPath: '__rspack_public_path__',
|
||||||
|
sourcemap: true
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DynamicBasePlugin = createUnplugin((options: DynamicBasePluginOptions = {}) => {
|
||||||
|
options = { ...defaults, ...options }
|
||||||
|
return {
|
||||||
|
name: 'nuxt:dynamic-base-path',
|
||||||
|
enforce: 'post',
|
||||||
|
transform (code, id) {
|
||||||
|
if (!id.includes('paths.mjs') || !code.includes('const appConfig = ')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const s = new MagicString(code)
|
||||||
|
s.append(`\n${options.globalPublicPath} = buildAssetsURL();\n`)
|
||||||
|
return {
|
||||||
|
code: s.toString(),
|
||||||
|
map: options.sourcemap
|
||||||
|
? s.generateMap({ source: id, includeContent: true })
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
137
packages/rspack/src/plugins/vue/client.ts
Normal file
137
packages/rspack/src/plugins/vue/client.ts
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
/**
|
||||||
|
* This file is based on Vue.js (MIT) webpack plugins
|
||||||
|
* https://github.com/vuejs/vue/blob/dev/src/server/webpack-plugin/client.js
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { normalizeWebpackManifest } from 'vue-bundle-renderer'
|
||||||
|
import { dirname } from 'pathe'
|
||||||
|
import hash from 'hash-sum'
|
||||||
|
import { uniq } from 'lodash-es'
|
||||||
|
import fse from 'fs-extra'
|
||||||
|
|
||||||
|
import type { Nuxt } from '@nuxt/schema'
|
||||||
|
import type { Compilation, Compiler } from '@rspack/core'
|
||||||
|
import { isJS, isCSS, isHotUpdate } from './util'
|
||||||
|
|
||||||
|
interface PluginOptions {
|
||||||
|
filename: string
|
||||||
|
nuxt: Nuxt
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class VueSSRClientPlugin {
|
||||||
|
options: PluginOptions
|
||||||
|
|
||||||
|
constructor (options: PluginOptions) {
|
||||||
|
this.options = Object.assign({
|
||||||
|
filename: null
|
||||||
|
}, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
apply (compiler: Compiler) {
|
||||||
|
compiler.hooks.afterEmit.tap('VueSSRClientPlugin', async (compilation: Compilation) => {
|
||||||
|
const stats = compilation.getStats().toJson()
|
||||||
|
|
||||||
|
const allFiles = uniq(stats.assets!
|
||||||
|
.map(a => a.name))
|
||||||
|
.filter(file => !isHotUpdate(file))
|
||||||
|
|
||||||
|
const initialFiles = uniq(Object.keys(stats.entrypoints!)
|
||||||
|
.map(name => stats.entrypoints![name].assets!)
|
||||||
|
.reduce((files, entryAssets) => files.concat(entryAssets.map(entryAsset => entryAsset.name)), [] as string[])
|
||||||
|
.filter(file => isJS(file) || isCSS(file)))
|
||||||
|
.filter(file => !isHotUpdate(file))
|
||||||
|
|
||||||
|
const asyncFiles = allFiles
|
||||||
|
.filter(file => isJS(file) || isCSS(file))
|
||||||
|
.filter(file => !initialFiles.includes(file))
|
||||||
|
.filter(file => !isHotUpdate(file))
|
||||||
|
|
||||||
|
const assetsMapping: Record<string, string[]> = {}
|
||||||
|
stats.assets!
|
||||||
|
.filter(({ name }) => isJS(name))
|
||||||
|
.filter(({ name }) => !isHotUpdate(name))
|
||||||
|
.forEach(({ name, chunkNames = [] }) => {
|
||||||
|
const componentHash = hash(chunkNames.join('|'))
|
||||||
|
if (!assetsMapping[componentHash]) {
|
||||||
|
assetsMapping[componentHash] = []
|
||||||
|
}
|
||||||
|
assetsMapping[componentHash].push(name)
|
||||||
|
})
|
||||||
|
|
||||||
|
const webpackManifest = {
|
||||||
|
publicPath: stats.publicPath,
|
||||||
|
all: allFiles,
|
||||||
|
initial: initialFiles,
|
||||||
|
async: asyncFiles,
|
||||||
|
modules: { /* [identifier: string]: Array<index: number> */ } as Record<string, number[]>,
|
||||||
|
assetsMapping
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.log(stats.modules)
|
||||||
|
|
||||||
|
const { entrypoints = {}, namedChunkGroups = {} } = stats
|
||||||
|
const assetModules = stats.modules!.filter(m => m.assets?.length)
|
||||||
|
const fileToIndex = (file: string) => webpackManifest.all.indexOf(file)
|
||||||
|
stats.modules!.forEach((m) => {
|
||||||
|
// Ignore modules duplicated in multiple chunks
|
||||||
|
if (m.chunks!.length === 1) {
|
||||||
|
const [cid] = m.chunks!
|
||||||
|
const chunk = stats.chunks!.find(c => c.id === cid)
|
||||||
|
if (!chunk || !chunk.files) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const id = m.identifier!.replace(/\s\w+$/, '') // remove appended hash
|
||||||
|
const filesSet = new Set(chunk.files.map(fileToIndex).filter(i => i !== -1))
|
||||||
|
|
||||||
|
for (const chunkName of chunk.names!) {
|
||||||
|
if (!entrypoints[chunkName]) {
|
||||||
|
const chunkGroup = namedChunkGroups[chunkName]
|
||||||
|
if (chunkGroup) {
|
||||||
|
for (const asset of chunkGroup.assets!) {
|
||||||
|
filesSet.add(fileToIndex(asset.name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = Array.from(filesSet)
|
||||||
|
webpackManifest.modules[hash(id)] = files
|
||||||
|
|
||||||
|
// In production mode, modules may be concatenated by scope hoisting
|
||||||
|
// Include ConcatenatedModule for not losing module-component mapping
|
||||||
|
if (Array.isArray(m.modules)) {
|
||||||
|
for (const concatenatedModule of m.modules) {
|
||||||
|
const id = hash(concatenatedModule.identifier!.replace(/\s\w+$/, ''))
|
||||||
|
if (!webpackManifest.modules[id]) {
|
||||||
|
webpackManifest.modules[id] = files
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all asset modules associated with the same chunk
|
||||||
|
assetModules.forEach((m) => {
|
||||||
|
if (m.chunks!.includes(cid)) {
|
||||||
|
files.push(...(m.assets as string[])?.map(fileToIndex))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const manifest = normalizeWebpackManifest(webpackManifest as any)
|
||||||
|
await this.options.nuxt.callHook('build:manifest', manifest)
|
||||||
|
|
||||||
|
const src = JSON.stringify(manifest, null, 2)
|
||||||
|
|
||||||
|
await fse.mkdirp(dirname(this.options.filename))
|
||||||
|
await fse.writeFile(this.options.filename, src)
|
||||||
|
|
||||||
|
const mjsSrc = 'export default ' + src
|
||||||
|
await fse.writeFile(this.options.filename.replace('.json', '.mjs'), mjsSrc)
|
||||||
|
|
||||||
|
// assets[this.options.filename] = {
|
||||||
|
// source: () => src,
|
||||||
|
// size: () => src.length
|
||||||
|
// }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
90
packages/rspack/src/plugins/vue/server.ts
Normal file
90
packages/rspack/src/plugins/vue/server.ts
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import type { Compilation, Compiler } from '@rspack/core'
|
||||||
|
import webpack from '@rspack/core'
|
||||||
|
import { validate, isJS, extractQueryPartJS } from './util'
|
||||||
|
|
||||||
|
export interface VueSSRServerPluginOptions {
|
||||||
|
filename: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class VueSSRServerPlugin {
|
||||||
|
options: VueSSRServerPluginOptions
|
||||||
|
|
||||||
|
constructor (options: Partial<VueSSRServerPluginOptions> = {}) {
|
||||||
|
this.options = Object.assign({
|
||||||
|
filename: null
|
||||||
|
}, options) as VueSSRServerPluginOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
apply (compiler: Compiler) {
|
||||||
|
validate(compiler)
|
||||||
|
compiler.hooks.make.tap('VueSSRServerPlugin', (compilation: Compilation) => {
|
||||||
|
compilation.hooks.processAssets.tapAsync({
|
||||||
|
name: 'VueSSRServerPlugin',
|
||||||
|
stage: webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL
|
||||||
|
}, (assets: any, cb: any) => {
|
||||||
|
const stats = compilation.getStats().toJson()
|
||||||
|
const [entryName] = Object.keys(stats.entrypoints!)
|
||||||
|
const entryInfo = stats.entrypoints![entryName]
|
||||||
|
|
||||||
|
if (!entryInfo) {
|
||||||
|
// #5553
|
||||||
|
return cb()
|
||||||
|
}
|
||||||
|
|
||||||
|
const entryAssets = entryInfo.assets!.filter((asset: { name: string }) => isJS(asset.name))
|
||||||
|
|
||||||
|
if (entryAssets.length > 1) {
|
||||||
|
throw new Error(
|
||||||
|
'Server-side bundle should have one single entry file. ' +
|
||||||
|
'Avoid using CommonsChunkPlugin in the server config.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const [entry] = entryAssets
|
||||||
|
if (!entry || typeof entry.name !== 'string') {
|
||||||
|
throw new Error(
|
||||||
|
`Entry "${entryName}" not found. Did you specify the correct entry option?`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const bundle = {
|
||||||
|
entry: entry.name,
|
||||||
|
files: {} as Record<string, string>,
|
||||||
|
maps: {} as Record<string, string>
|
||||||
|
}
|
||||||
|
|
||||||
|
stats.assets!.forEach((asset: any) => {
|
||||||
|
if (isJS(asset.name)) {
|
||||||
|
const queryPart = extractQueryPartJS(asset.name)
|
||||||
|
if (queryPart !== undefined) {
|
||||||
|
bundle.files[asset.name] = asset.name.replace(queryPart, '')
|
||||||
|
} else {
|
||||||
|
bundle.files[asset.name] = asset.name
|
||||||
|
}
|
||||||
|
} else if (asset.name.match(/\.js\.map$/)) {
|
||||||
|
bundle.maps[asset.name.replace(/\.map$/, '')] = asset.name
|
||||||
|
} else {
|
||||||
|
// Do not emit non-js assets for server
|
||||||
|
delete assets[asset.name]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const src = JSON.stringify(bundle, null, 2)
|
||||||
|
|
||||||
|
assets[this.options.filename] = {
|
||||||
|
source: () => src,
|
||||||
|
size: () => src.length
|
||||||
|
}
|
||||||
|
|
||||||
|
const mjsSrc = 'export default ' + src
|
||||||
|
assets[this.options.filename.replace('.json', '.mjs')] = {
|
||||||
|
source: () => mjsSrc,
|
||||||
|
map: () => null,
|
||||||
|
size: () => mjsSrc.length
|
||||||
|
}
|
||||||
|
|
||||||
|
cb()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
30
packages/rspack/src/plugins/vue/util.ts
Normal file
30
packages/rspack/src/plugins/vue/util.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* This file is based on Vue.js (MIT) webpack plugins
|
||||||
|
* https://github.com/vuejs/vue/blob/dev/src/server/webpack-plugin/util.js
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { logger } from '@nuxt/kit'
|
||||||
|
import type { Compiler } from '@rspack/core'
|
||||||
|
|
||||||
|
export const validate = (compiler: Compiler) => {
|
||||||
|
if (compiler.options.target !== 'node') {
|
||||||
|
logger.warn('webpack config `target` should be "node".')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!compiler.options.externals) {
|
||||||
|
logger.info(
|
||||||
|
'It is recommended to externalize dependencies in the server build for ' +
|
||||||
|
'better build performance.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const isJSRegExp = /\.[cm]?js(\?[^.]+)?$/
|
||||||
|
|
||||||
|
export const isJS = (file: string) => isJSRegExp.test(file)
|
||||||
|
|
||||||
|
export const extractQueryPartJS = (file: string) => isJSRegExp.exec(file)?.[1]
|
||||||
|
|
||||||
|
export const isCSS = (file: string) => /\.css(\?[^.]+)?$/.test(file)
|
||||||
|
|
||||||
|
export const isHotUpdate = (file: string) => file.includes('hot-update')
|
17
packages/rspack/src/plugins/warning-ignore.ts
Normal file
17
packages/rspack/src/plugins/warning-ignore.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import type { Compiler, WebpackError } from '@rspack/core'
|
||||||
|
|
||||||
|
export type WarningFilter = (warn: WebpackError) => boolean
|
||||||
|
|
||||||
|
export default class WarningIgnorePlugin {
|
||||||
|
filter: WarningFilter
|
||||||
|
|
||||||
|
constructor (filter: WarningFilter) {
|
||||||
|
this.filter = filter
|
||||||
|
}
|
||||||
|
|
||||||
|
apply (compiler: Compiler) {
|
||||||
|
compiler.hooks.done.tap('warnfix-plugin', (stats) => {
|
||||||
|
stats.compilation.warnings = stats.compilation.warnings.filter(this.filter)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
18
packages/rspack/src/presets/assets.ts
Normal file
18
packages/rspack/src/presets/assets.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import type { RspackConfigContext } from '../utils/config'
|
||||||
|
|
||||||
|
export function assets (ctx: RspackConfigContext) {
|
||||||
|
ctx.config.module!.rules!.push(
|
||||||
|
{
|
||||||
|
test: /\.(png|jpe?g|gif|svg|webp)$/i,
|
||||||
|
type: 'asset/resource'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i,
|
||||||
|
type: 'asset/resource'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.(webm|mp4|ogv)$/i,
|
||||||
|
type: 'asset/resource'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
260
packages/rspack/src/presets/base.ts
Normal file
260
packages/rspack/src/presets/base.ts
Normal file
@ -0,0 +1,260 @@
|
|||||||
|
import { resolve, normalize } from 'pathe'
|
||||||
|
// @ts-expect-error missing types
|
||||||
|
import TimeFixPlugin from 'time-fix-plugin'
|
||||||
|
import WebpackBar from 'webpackbar'
|
||||||
|
import type { Configuration } from '@rspack/core'
|
||||||
|
import type webpack from '@rspack/core'
|
||||||
|
import { logger } from '@nuxt/kit'
|
||||||
|
// @ts-expect-error missing types
|
||||||
|
import FriendlyErrorsWebpackPlugin from '@nuxt/friendly-errors-webpack-plugin'
|
||||||
|
import escapeRegExp from 'escape-string-regexp'
|
||||||
|
import { joinURL } from 'ufo'
|
||||||
|
import type { NuxtOptions } from '@nuxt/schema'
|
||||||
|
import type { WarningFilter } from '../plugins/warning-ignore'
|
||||||
|
import WarningIgnorePlugin from '../plugins/warning-ignore'
|
||||||
|
import type { RspackConfigContext } from '../utils/config'
|
||||||
|
import { applyPresets, fileName } from '../utils/config'
|
||||||
|
|
||||||
|
export function base (ctx: RspackConfigContext) {
|
||||||
|
applyPresets(ctx, [
|
||||||
|
baseAlias,
|
||||||
|
baseConfig,
|
||||||
|
basePlugins,
|
||||||
|
baseResolve,
|
||||||
|
baseTranspile
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
function baseConfig (ctx: RspackConfigContext) {
|
||||||
|
const { options } = ctx
|
||||||
|
|
||||||
|
ctx.config = {
|
||||||
|
name: ctx.name,
|
||||||
|
entry: { app: [resolve(options.appDir, options.experimental.asyncEntry ? 'entry.async' : 'entry')] },
|
||||||
|
module: { rules: [] },
|
||||||
|
plugins: [],
|
||||||
|
// TODO:
|
||||||
|
// externals: [],
|
||||||
|
// optimization: {
|
||||||
|
// ...options.webpack.optimization,
|
||||||
|
// minimizer: []
|
||||||
|
// },
|
||||||
|
experiments: {},
|
||||||
|
mode: ctx.isDev ? 'development' : 'production',
|
||||||
|
cache: getCache(ctx),
|
||||||
|
output: getOutput(ctx),
|
||||||
|
stats: statsMap[ctx.nuxt.options.logLevel] ?? statsMap.info,
|
||||||
|
...ctx.config
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function basePlugins (ctx: RspackConfigContext) {
|
||||||
|
const { config, options, nuxt } = ctx
|
||||||
|
|
||||||
|
config.plugins = config.plugins || []
|
||||||
|
|
||||||
|
// Add timefix-plugin before other plugins
|
||||||
|
if (options.dev) {
|
||||||
|
// config.plugins.push(new TimeFixPlugin())
|
||||||
|
}
|
||||||
|
|
||||||
|
// User plugins
|
||||||
|
config.plugins.push(...(options.webpack.plugins || []))
|
||||||
|
|
||||||
|
// Ignore empty warnings
|
||||||
|
// config.plugins.push(new WarningIgnorePlugin(getWarningIgnoreFilter(ctx)))
|
||||||
|
|
||||||
|
// Provide env
|
||||||
|
config.builtins = config.builtins || {}
|
||||||
|
config.builtins.noEmitAssets = false
|
||||||
|
config.builtins.define = {
|
||||||
|
...getEnv(ctx),
|
||||||
|
...config.builtins.define
|
||||||
|
}
|
||||||
|
|
||||||
|
// Friendly errors
|
||||||
|
// if (ctx.isServer || (ctx.isDev && options.webpack.friendlyErrors)) {
|
||||||
|
// config.plugins.push(
|
||||||
|
// new FriendlyErrorsWebpackPlugin({
|
||||||
|
// clearConsole: false,
|
||||||
|
// reporter: 'consola',
|
||||||
|
// logLevel: 'ERROR' // TODO
|
||||||
|
// })
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (nuxt.options.webpack.profile) {
|
||||||
|
// // Webpackbar
|
||||||
|
// const colors = {
|
||||||
|
// client: 'green',
|
||||||
|
// server: 'orange',
|
||||||
|
// modern: 'blue'
|
||||||
|
// }
|
||||||
|
// config.plugins.push(new WebpackBar({
|
||||||
|
// name: ctx.name,
|
||||||
|
// color: colors[ctx.name as keyof typeof colors],
|
||||||
|
// reporters: ['stats'],
|
||||||
|
// stats: !ctx.isDev,
|
||||||
|
// reporter: {
|
||||||
|
// // @ts-ignore
|
||||||
|
// change: (_, { shortPath }) => {
|
||||||
|
// if (!ctx.isServer) {
|
||||||
|
// nuxt.callHook('rspack:change', shortPath)
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// // @ts-ignore
|
||||||
|
// done: ({ state }) => {
|
||||||
|
// if (state.hasErrors) {
|
||||||
|
// nuxt.callHook('rspack:error')
|
||||||
|
// } else {
|
||||||
|
// logger.success(`${state.name} ${state.message}`)
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// allDone: () => {
|
||||||
|
// nuxt.callHook('rspack:done')
|
||||||
|
// },
|
||||||
|
// // @ts-ignore
|
||||||
|
// progress ({ statesArray }) {
|
||||||
|
// nuxt.callHook('rspack:progress', statesArray)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }))
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
function baseAlias (ctx: RspackConfigContext) {
|
||||||
|
const { options } = ctx
|
||||||
|
|
||||||
|
ctx.alias = {
|
||||||
|
'#app': options.appDir,
|
||||||
|
'#build/plugins': resolve(options.buildDir, 'plugins', ctx.isClient ? 'client' : 'server'),
|
||||||
|
'#build': options.buildDir,
|
||||||
|
...options.alias,
|
||||||
|
...ctx.alias
|
||||||
|
}
|
||||||
|
if (ctx.isClient) {
|
||||||
|
ctx.alias['#internal/nitro'] = resolve(ctx.nuxt.options.buildDir, 'nitro.client.mjs')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function baseResolve (ctx: RspackConfigContext) {
|
||||||
|
const { options, config } = ctx
|
||||||
|
|
||||||
|
// Prioritize nested node_modules in webpack search path (#2558)
|
||||||
|
// TODO: this might be refactored as default modulesDir?
|
||||||
|
const webpackModulesDir = ['node_modules'].concat(options.modulesDir)
|
||||||
|
|
||||||
|
config.resolve = {
|
||||||
|
extensions: ['.wasm', '.mjs', '.js', '.ts', '.json', '.vue', '.jsx', '.tsx'],
|
||||||
|
alias: ctx.alias,
|
||||||
|
modules: webpackModulesDir,
|
||||||
|
// fullySpecified: false,
|
||||||
|
...config.resolve
|
||||||
|
}
|
||||||
|
|
||||||
|
// config.resolveLoader = {
|
||||||
|
// modules: webpackModulesDir,
|
||||||
|
// ...config.resolveLoader
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function baseTranspile (ctx: RspackConfigContext) {
|
||||||
|
const { options } = ctx
|
||||||
|
|
||||||
|
const transpile = [
|
||||||
|
/\.vue\.js/i, // include SFCs in node_modules
|
||||||
|
/consola\/src/,
|
||||||
|
/vue-demi/
|
||||||
|
]
|
||||||
|
|
||||||
|
for (let pattern of options.build.transpile) {
|
||||||
|
if (typeof pattern === 'function') {
|
||||||
|
const result = pattern(ctx)
|
||||||
|
if (result) { pattern = result }
|
||||||
|
}
|
||||||
|
if (typeof pattern === 'string') {
|
||||||
|
transpile.push(new RegExp(escapeRegExp(normalize(pattern))))
|
||||||
|
} else if (pattern instanceof RegExp) {
|
||||||
|
transpile.push(pattern)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: unique
|
||||||
|
ctx.transpile = [...transpile, ...ctx.transpile]
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCache (ctx: RspackConfigContext): Configuration['cache'] {
|
||||||
|
const { options } = ctx
|
||||||
|
|
||||||
|
if (!options.dev) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Disable for nuxt internal dev due to inconsistencies
|
||||||
|
// return {
|
||||||
|
// name: ctx.name,
|
||||||
|
// type: 'filesystem',
|
||||||
|
// cacheDirectory: resolve(ctx.options.rootDir, 'node_modules/.cache/webpack'),
|
||||||
|
// managedPaths: [
|
||||||
|
// ...ctx.options.modulesDir
|
||||||
|
// ],
|
||||||
|
// buildDependencies: {
|
||||||
|
// config: [
|
||||||
|
// ...ctx.options._nuxtConfigFiles
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOutput (ctx: RspackConfigContext): Configuration['output'] {
|
||||||
|
const { options } = ctx
|
||||||
|
|
||||||
|
return {
|
||||||
|
path: resolve(options.buildDir, 'dist', ctx.isServer ? 'server' : joinURL('client', options.app.buildAssetsDir)),
|
||||||
|
filename: fileName(ctx, 'app'),
|
||||||
|
chunkFilename: fileName(ctx, 'chunk'),
|
||||||
|
publicPath: joinURL(options.app.baseURL, options.app.buildAssetsDir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getWarningIgnoreFilter (ctx: RspackConfigContext): WarningFilter {
|
||||||
|
const { options } = ctx
|
||||||
|
|
||||||
|
const filters: WarningFilter[] = [
|
||||||
|
// Hide warnings about plugins without a default export (#1179)
|
||||||
|
warn => warn.name === 'ModuleDependencyWarning' &&
|
||||||
|
warn.message.includes('export \'default\'') &&
|
||||||
|
warn.message.includes('nuxt_plugin_'),
|
||||||
|
...(options.webpack.warningIgnoreFilters || [])
|
||||||
|
]
|
||||||
|
|
||||||
|
return warn => !filters.some(ignoreFilter => ignoreFilter(warn))
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEnv (ctx: RspackConfigContext) {
|
||||||
|
const { options } = ctx
|
||||||
|
|
||||||
|
const _env: Record<string, string | boolean> = {
|
||||||
|
'process.env.NODE_ENV': JSON.stringify(ctx.config.mode),
|
||||||
|
'process.mode': JSON.stringify(ctx.config.mode),
|
||||||
|
'process.dev': options.dev,
|
||||||
|
__NUXT_VERSION__: JSON.stringify(ctx.nuxt._version),
|
||||||
|
'process.env.VUE_ENV': JSON.stringify(ctx.name),
|
||||||
|
'process.browser': ctx.isClient,
|
||||||
|
'process.client': ctx.isClient,
|
||||||
|
'process.server': ctx.isServer
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.webpack.aggressiveCodeRemoval) {
|
||||||
|
_env['typeof process'] = JSON.stringify(ctx.isServer ? 'object' : 'undefined')
|
||||||
|
_env['typeof window'] = _env['typeof document'] = JSON.stringify(!ctx.isServer ? 'object' : 'undefined')
|
||||||
|
}
|
||||||
|
|
||||||
|
return _env
|
||||||
|
}
|
||||||
|
|
||||||
|
const statsMap: Record<NuxtOptions['logLevel'], Configuration['stats']> = {
|
||||||
|
silent: 'none',
|
||||||
|
info: 'normal',
|
||||||
|
verbose: 'verbose'
|
||||||
|
}
|
45
packages/rspack/src/presets/esbuild.ts
Normal file
45
packages/rspack/src/presets/esbuild.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { EsbuildPlugin } from 'esbuild-loader'
|
||||||
|
import type { RspackConfigContext } from '../utils/config'
|
||||||
|
|
||||||
|
export function esbuild (ctx: RspackConfigContext) {
|
||||||
|
const { config } = ctx
|
||||||
|
|
||||||
|
// https://esbuild.github.io/getting-started/#bundling-for-the-browser
|
||||||
|
// https://gs.statcounter.com/browser-version-market-share
|
||||||
|
// https://nodejs.org/en/
|
||||||
|
const target = ctx.isServer ? 'es2019' : 'chrome85'
|
||||||
|
|
||||||
|
// https://github.com/nuxt/nuxt/issues/13052
|
||||||
|
// config.optimization!.minimizer!.push(new EsbuildPlugin())
|
||||||
|
|
||||||
|
config.module!.rules!.push(
|
||||||
|
// {
|
||||||
|
// test: /\.m?[jt]s$/i,
|
||||||
|
// type: 'javascript/auto'
|
||||||
|
// // loader: 'esbuild-loader',
|
||||||
|
// // exclude: (file) => {
|
||||||
|
// // // Not exclude files outside node_modules
|
||||||
|
// // file = file.split('node_modules', 2)[1]
|
||||||
|
// // if (!file) { return false }
|
||||||
|
|
||||||
|
// // return !ctx.transpile.some(module => module.test(file))
|
||||||
|
// // },
|
||||||
|
// // resolve: {
|
||||||
|
// // // fullySpecified: false
|
||||||
|
// // },
|
||||||
|
// // options: {
|
||||||
|
// // loader: 'ts',
|
||||||
|
// // target
|
||||||
|
// // }
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// test: /\.m?[jt]sx$/,
|
||||||
|
// // loader: 'esbuild-loader',
|
||||||
|
// type: 'jsx'
|
||||||
|
// // options: {
|
||||||
|
// // loader: 'tsx',
|
||||||
|
// // target
|
||||||
|
// // }
|
||||||
|
// }
|
||||||
|
)
|
||||||
|
}
|
37
packages/rspack/src/presets/node.ts
Normal file
37
packages/rspack/src/presets/node.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import type { RspackConfigContext } from '../utils/config'
|
||||||
|
|
||||||
|
export function node (ctx: RspackConfigContext) {
|
||||||
|
const { config } = ctx
|
||||||
|
|
||||||
|
config.target = 'node'
|
||||||
|
config.node = false
|
||||||
|
|
||||||
|
config.experiments!.outputModule = true
|
||||||
|
|
||||||
|
config.output = {
|
||||||
|
...config.output,
|
||||||
|
chunkFilename: '[name].mjs',
|
||||||
|
chunkFormat: 'module',
|
||||||
|
chunkLoading: 'import',
|
||||||
|
module: true,
|
||||||
|
environment: {
|
||||||
|
module: true,
|
||||||
|
arrowFunction: true,
|
||||||
|
bigIntLiteral: true,
|
||||||
|
const: true,
|
||||||
|
destructuring: true,
|
||||||
|
dynamicImport: true,
|
||||||
|
forOf: true
|
||||||
|
},
|
||||||
|
library: {
|
||||||
|
type: 'module'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
config.performance = {
|
||||||
|
...config.performance,
|
||||||
|
hints: false,
|
||||||
|
maxEntrypointSize: Infinity,
|
||||||
|
maxAssetSize: Infinity
|
||||||
|
}
|
||||||
|
}
|
20
packages/rspack/src/presets/nuxt.ts
Normal file
20
packages/rspack/src/presets/nuxt.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import type { RspackConfigContext } from '../utils/config'
|
||||||
|
import { applyPresets } from '../utils/config'
|
||||||
|
|
||||||
|
import { assets } from './assets'
|
||||||
|
import { base } from './base'
|
||||||
|
import { esbuild } from './esbuild'
|
||||||
|
import { pug } from './pug'
|
||||||
|
import { style } from './style'
|
||||||
|
import { vue } from './vue'
|
||||||
|
|
||||||
|
export function nuxt (ctx: RspackConfigContext) {
|
||||||
|
applyPresets(ctx, [
|
||||||
|
base,
|
||||||
|
assets,
|
||||||
|
esbuild,
|
||||||
|
pug,
|
||||||
|
style,
|
||||||
|
vue
|
||||||
|
])
|
||||||
|
}
|
25
packages/rspack/src/presets/pug.ts
Normal file
25
packages/rspack/src/presets/pug.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import type { RspackConfigContext } from '../utils/config'
|
||||||
|
|
||||||
|
export function pug (ctx: RspackConfigContext) {
|
||||||
|
ctx.config.module!.rules!.push({
|
||||||
|
test: /\.pug$/i,
|
||||||
|
oneOf: [
|
||||||
|
{
|
||||||
|
resourceQuery: /^\?vue/i,
|
||||||
|
use: [{
|
||||||
|
loader: 'pug-plain-loader',
|
||||||
|
options: ctx.options.webpack.loaders.pugPlain
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
use: [
|
||||||
|
'raw-loader',
|
||||||
|
{
|
||||||
|
loader: 'pug-plain-loader',
|
||||||
|
options: ctx.options.webpack.loaders.pugPlain
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
148
packages/rspack/src/presets/style.ts
Normal file
148
packages/rspack/src/presets/style.ts
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
import MiniCssExtractPlugin from 'mini-css-extract-plugin'
|
||||||
|
import CssMinimizerPlugin from 'css-minimizer-webpack-plugin'
|
||||||
|
import type { RspackConfigContext } from '../utils/config'
|
||||||
|
import { fileName, applyPresets } from '../utils/config'
|
||||||
|
import { getPostcssConfig } from '../utils/postcss'
|
||||||
|
|
||||||
|
export function style (ctx: RspackConfigContext) {
|
||||||
|
applyPresets(ctx, [
|
||||||
|
loaders,
|
||||||
|
extractCSS,
|
||||||
|
minimizer
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
function minimizer (ctx: RspackConfigContext) {
|
||||||
|
const { options, config } = ctx
|
||||||
|
|
||||||
|
// if (options.webpack.optimizeCSS && Array.isArray(config.optimization!.minimizer)) {
|
||||||
|
// config.optimization!.minimizer.push(new CssMinimizerPlugin({
|
||||||
|
// ...options.webpack.optimizeCSS
|
||||||
|
// }))
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractCSS (ctx: RspackConfigContext) {
|
||||||
|
const { options, config } = ctx
|
||||||
|
|
||||||
|
// CSS extraction
|
||||||
|
// if (options.webpack.extractCSS) {
|
||||||
|
// config.plugins!.push(new MiniCssExtractPlugin({
|
||||||
|
// filename: fileName(ctx, 'css'),
|
||||||
|
// chunkFilename: fileName(ctx, 'css'),
|
||||||
|
// ...options.webpack.extractCSS === true ? {} : options.webpack.extractCSS
|
||||||
|
// }))
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
function loaders (ctx: RspackConfigContext) {
|
||||||
|
const { config, options } = ctx
|
||||||
|
|
||||||
|
// CSS
|
||||||
|
config.module!.rules!.push(createdStyleRule('css', /\.css$/i, null, ctx))
|
||||||
|
|
||||||
|
// PostCSS
|
||||||
|
config.module!.rules!.push(createdStyleRule('postcss', /\.p(ost)?css$/i, null, ctx))
|
||||||
|
|
||||||
|
// Less
|
||||||
|
const lessLoader = { loader: 'less-loader', options: options.webpack.loaders.less }
|
||||||
|
config.module!.rules!.push(createdStyleRule('less', /\.less$/i, lessLoader, ctx))
|
||||||
|
|
||||||
|
// Sass (TODO: optional dependency)
|
||||||
|
const sassLoader = { loader: 'sass-loader', options: options.webpack.loaders.sass }
|
||||||
|
config.module!.rules!.push(createdStyleRule('sass', /\.sass$/i, sassLoader, ctx))
|
||||||
|
|
||||||
|
const scssLoader = { loader: 'sass-loader', options: options.webpack.loaders.scss }
|
||||||
|
config.module!.rules!.push(createdStyleRule('scss', /\.scss$/i, scssLoader, ctx))
|
||||||
|
|
||||||
|
// Stylus
|
||||||
|
const stylusLoader = { loader: 'stylus-loader', options: options.webpack.loaders.stylus }
|
||||||
|
config.module!.rules!.push(createdStyleRule('stylus', /\.styl(us)?$/i, stylusLoader, ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
function createdStyleRule (lang: string, test: RegExp, processorLoader: any, ctx: RspackConfigContext) {
|
||||||
|
const { options } = ctx
|
||||||
|
|
||||||
|
const styleLoaders = [
|
||||||
|
createPostcssLoadersRule(ctx),
|
||||||
|
processorLoader
|
||||||
|
].filter(Boolean)
|
||||||
|
|
||||||
|
options.webpack.loaders.css.importLoaders =
|
||||||
|
options.webpack.loaders.cssModules.importLoaders =
|
||||||
|
styleLoaders.length
|
||||||
|
|
||||||
|
const cssLoaders = createCssLoadersRule(ctx, options.webpack.loaders.css)
|
||||||
|
const cssModuleLoaders = createCssLoadersRule(ctx, options.webpack.loaders.cssModules)
|
||||||
|
|
||||||
|
return {
|
||||||
|
test,
|
||||||
|
oneOf: [
|
||||||
|
// This matches <style module>
|
||||||
|
{
|
||||||
|
resourceQuery: /module/,
|
||||||
|
use: cssModuleLoaders.concat(styleLoaders)
|
||||||
|
},
|
||||||
|
// This matches plain <style> or <style scoped>
|
||||||
|
{
|
||||||
|
use: cssLoaders.concat(styleLoaders)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createCssLoadersRule (ctx: RspackConfigContext, cssLoaderOptions: any) {
|
||||||
|
// const { options } = ctx
|
||||||
|
|
||||||
|
// const cssLoader = { loader: 'css-loader', options: cssLoaderOptions }
|
||||||
|
|
||||||
|
// if (options.webpack.extractCSS) {
|
||||||
|
// if (ctx.isServer) {
|
||||||
|
// // https://webpack.js.org/loaders/css-loader/#exportonlylocals
|
||||||
|
// if (cssLoader.options.modules) {
|
||||||
|
// cssLoader.options.modules.exportOnlyLocals = cssLoader.options.modules.exportOnlyLocals ?? true
|
||||||
|
// }
|
||||||
|
// return [cssLoader]
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return [
|
||||||
|
// {
|
||||||
|
// loader: MiniCssExtractPlugin.loader
|
||||||
|
// },
|
||||||
|
// cssLoader
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
|
||||||
|
return [
|
||||||
|
// https://github.com/vuejs/vue-style-loader/issues/56
|
||||||
|
// {
|
||||||
|
// loader: 'vue-style-loader',
|
||||||
|
// options: options.webpack.loaders.vueStyle
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// test: /\.css$/i,
|
||||||
|
// type: 'css'
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// test: /\.module\.css$/i,
|
||||||
|
// type: 'css/module' // this is enabled by default for module.css, so you don't need to specify it
|
||||||
|
// }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
function createPostcssLoadersRule (ctx: RspackConfigContext) {
|
||||||
|
const { options, nuxt } = ctx
|
||||||
|
|
||||||
|
if (!options.postcss) { return }
|
||||||
|
|
||||||
|
const config = getPostcssConfig(nuxt)
|
||||||
|
|
||||||
|
if (!config) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
loader: 'postcss-loader',
|
||||||
|
options: config
|
||||||
|
}
|
||||||
|
}
|
42
packages/rspack/src/presets/vue.ts
Normal file
42
packages/rspack/src/presets/vue.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { resolve } from 'pathe'
|
||||||
|
import VueLoaderPlugin from 'vue-loader/dist/pluginWebpack5.js'
|
||||||
|
import VueSSRClientPlugin from '../plugins/vue/client'
|
||||||
|
import VueSSRServerPlugin from '../plugins/vue/server'
|
||||||
|
import type { RspackConfigContext } from '../utils/config'
|
||||||
|
|
||||||
|
export function vue (ctx: RspackConfigContext) {
|
||||||
|
const { options, config } = ctx
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
// config.plugins.push(new (VueLoaderPlugin.default || VueLoaderPlugin)())
|
||||||
|
|
||||||
|
config.module!.rules!.push({
|
||||||
|
test: /\.vue$/i,
|
||||||
|
use: ['/Users/daniel/code/nuxt.js/packages/rspack/src/loaders/vue.cjs']
|
||||||
|
// options: {
|
||||||
|
// reactivityTransform: ctx.nuxt.options.experimental.reactivityTransform,
|
||||||
|
// ...options.webpack.loaders.vue
|
||||||
|
// }
|
||||||
|
})
|
||||||
|
|
||||||
|
if (ctx.isClient) {
|
||||||
|
config.plugins!.push(new VueSSRClientPlugin({
|
||||||
|
filename: resolve(options.buildDir, 'dist/server', `${ctx.name}.manifest.json`),
|
||||||
|
nuxt: ctx.nuxt
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
config.plugins!.push(new VueSSRServerPlugin({
|
||||||
|
filename: `${ctx.name}.manifest.json`
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Feature flags
|
||||||
|
// https://github.com/vuejs/vue-next/tree/master/packages/vue#bundler-build-feature-flags
|
||||||
|
// TODO: Provide options to toggle
|
||||||
|
config.builtins = config.builtins || {}
|
||||||
|
config.builtins.define = {
|
||||||
|
__VUE_OPTIONS_API__: 'true',
|
||||||
|
__VUE_PROD_DEVTOOLS__: 'false',
|
||||||
|
...config.builtins.define
|
||||||
|
}
|
||||||
|
}
|
176
packages/rspack/src/rspack.ts
Normal file
176
packages/rspack/src/rspack.ts
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
import pify from 'pify'
|
||||||
|
import { createCompiler } from '@rspack/core'
|
||||||
|
import type { NodeMiddleware } from 'h3'
|
||||||
|
import { fromNodeMiddleware, defineEventHandler, useBase } from 'h3'
|
||||||
|
import type { OutputFileSystem } from '@rspack/dev-middleware'
|
||||||
|
import rspackDevMiddleware, { getRspackMemoryAssets } from '@rspack/dev-middleware'
|
||||||
|
import webpackHotMiddleware from 'webpack-hot-middleware'
|
||||||
|
import type { Compiler, Watching } from '@rspack/core'
|
||||||
|
|
||||||
|
import type { Nuxt } from '@nuxt/schema'
|
||||||
|
import { joinURL } from 'ufo'
|
||||||
|
import { logger, useNuxt } from '@nuxt/kit'
|
||||||
|
import { createUnplugin } from 'unplugin'
|
||||||
|
import { composableKeysPlugin } from '../../vite/src/plugins/composable-keys'
|
||||||
|
import { DynamicBasePlugin } from './plugins/dynamic-base'
|
||||||
|
import { ChunkErrorPlugin } from './plugins/chunk'
|
||||||
|
import { createMFS } from './utils/mfs'
|
||||||
|
import { registerVirtualModules } from './virtual-modules'
|
||||||
|
import { client, server } from './configs'
|
||||||
|
import { createRspackConfigContext, applyPresets, getRspackConfig } from './utils/config'
|
||||||
|
|
||||||
|
// TODO: Support plugins
|
||||||
|
// const plugins: string[] = []
|
||||||
|
|
||||||
|
export async function bundle (nuxt: Nuxt) {
|
||||||
|
// TODO: remove when we have support for virtual modules in rspack
|
||||||
|
nuxt.hook('app:templates', (app) => {
|
||||||
|
for (const template of app.templates) {
|
||||||
|
template.write = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const webpackConfigs = [client, ...nuxt.options.ssr ? [server] : []].map((preset) => {
|
||||||
|
const ctx = createRspackConfigContext(nuxt)
|
||||||
|
applyPresets(ctx, preset)
|
||||||
|
return getRspackConfig(ctx)
|
||||||
|
})
|
||||||
|
|
||||||
|
await nuxt.callHook('rspack:config', webpackConfigs)
|
||||||
|
|
||||||
|
// console.log(webpackConfigs[0])
|
||||||
|
|
||||||
|
// // Initialize shared MFS for dev
|
||||||
|
const mfs = nuxt.options.dev ? createMFS() : null
|
||||||
|
|
||||||
|
// Configure compilers
|
||||||
|
const compilers = webpackConfigs.map((config) => {
|
||||||
|
// TODO: need support for runtime __webpack_public_path__
|
||||||
|
// config.plugins!.push(DynamicBasePlugin.rspack({
|
||||||
|
// sourcemap: nuxt.options.sourcemap[config.name as 'client' | 'server']
|
||||||
|
// }))
|
||||||
|
// TODO: Emit chunk errors if the user has opted in to `experimental.emitRouteChunkError`
|
||||||
|
// if (config.name === 'client' && nuxt.options.experimental.emitRouteChunkError) {
|
||||||
|
// config.plugins!.push(new ChunkErrorPlugin())
|
||||||
|
// }
|
||||||
|
// config.plugins!.push(composableKeysPlugin.rspack({
|
||||||
|
// sourcemap: nuxt.options.sourcemap[config.name as 'client' | 'server'],
|
||||||
|
// rootDir: nuxt.options.rootDir,
|
||||||
|
// composables: nuxt.options.optimization.keyedComposables
|
||||||
|
// }))
|
||||||
|
|
||||||
|
// Create compiler
|
||||||
|
const compiler = createCompiler(config)
|
||||||
|
|
||||||
|
// In dev, write files in memory FS
|
||||||
|
if (nuxt.options.dev) {
|
||||||
|
compiler.outputFileSystem = mfs as unknown as OutputFileSystem
|
||||||
|
}
|
||||||
|
|
||||||
|
return compiler
|
||||||
|
})
|
||||||
|
|
||||||
|
nuxt.hook('close', async () => {
|
||||||
|
for (const compiler of compilers) {
|
||||||
|
await new Promise<void>(resolve => compiler.close(resolve))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Start Builds
|
||||||
|
if (nuxt.options.dev) {
|
||||||
|
return Promise.all(compilers.map(c => compile(c)))
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const c of compilers) {
|
||||||
|
await compile(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createDevMiddleware (compiler: Compiler) {
|
||||||
|
const nuxt = useNuxt()
|
||||||
|
|
||||||
|
logger.debug('Creating webpack middleware...')
|
||||||
|
|
||||||
|
// Create webpack dev middleware
|
||||||
|
const devMiddleware = rspackDevMiddleware(compiler as any, {
|
||||||
|
publicPath: joinURL(nuxt.options.app.baseURL, nuxt.options.app.buildAssetsDir),
|
||||||
|
outputFileSystem: compiler.outputFileSystem as any,
|
||||||
|
stats: 'none',
|
||||||
|
serverSideRender: true,
|
||||||
|
...nuxt.options.webpack.devMiddleware
|
||||||
|
})
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
nuxt.hook('close', () => pify(devMiddleware.close.bind(devMiddleware))())
|
||||||
|
|
||||||
|
// const { client: _client, ...hotMiddlewareOptions } = nuxt.options.webpack.hotMiddleware || {}
|
||||||
|
// const hotMiddleware = webpackHotMiddleware(compiler, {
|
||||||
|
// log: false,
|
||||||
|
// heartbeat: 10000,
|
||||||
|
// path: joinURL(nuxt.options.app.baseURL, '__webpack_hmr', compiler.options.name!),
|
||||||
|
// ...hotMiddlewareOptions
|
||||||
|
// })
|
||||||
|
|
||||||
|
// Register devMiddleware on server
|
||||||
|
const devHandler = fromNodeMiddleware(getRspackMemoryAssets(compiler, devMiddleware))
|
||||||
|
// const hotHandler = fromNodeMiddleware(hotMiddleware as NodeMiddleware)
|
||||||
|
await nuxt.callHook('server:devHandler', defineEventHandler(async (event) => {
|
||||||
|
await devHandler(event)
|
||||||
|
// await hotHandler(event)
|
||||||
|
}))
|
||||||
|
|
||||||
|
return devMiddleware
|
||||||
|
}
|
||||||
|
|
||||||
|
async function compile (compiler: Compiler) {
|
||||||
|
const nuxt = useNuxt()
|
||||||
|
|
||||||
|
const { name } = compiler.options
|
||||||
|
|
||||||
|
await nuxt.callHook('rspack:compile', { name: name!, compiler })
|
||||||
|
|
||||||
|
// Load renderer resources after build
|
||||||
|
compiler.hooks.done.tap('load-resources', async (stats) => {
|
||||||
|
await nuxt.callHook('rspack:compiled', { name: name!, compiler, stats })
|
||||||
|
})
|
||||||
|
|
||||||
|
// --- Dev Build ---
|
||||||
|
if (nuxt.options.dev) {
|
||||||
|
const compilersWatching: Watching[] = []
|
||||||
|
|
||||||
|
nuxt.hook('close', async () => {
|
||||||
|
await Promise.all(compilersWatching.map(watching => pify(watching.close.bind(watching))()))
|
||||||
|
})
|
||||||
|
|
||||||
|
// Client build
|
||||||
|
if (name === 'client') {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
compiler.hooks.done.tap('nuxt-dev', () => { resolve(null) })
|
||||||
|
compiler.hooks.failed.tap('nuxt-errorlog', (err) => { reject(err) })
|
||||||
|
// Start watch
|
||||||
|
createDevMiddleware(compiler).then((devMiddleware) => {
|
||||||
|
compilersWatching.push(devMiddleware.context.watching)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server, build and watch for changes
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const watching = compiler.watch(nuxt.options.watchers.webpack, (err) => {
|
||||||
|
if (err) { return reject(err) }
|
||||||
|
resolve(null)
|
||||||
|
})
|
||||||
|
|
||||||
|
compilersWatching.push(watching)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Production Build ---
|
||||||
|
const stats = await new Promise<webpack.Stats>((resolve, reject) => compiler.run((err, stats) => err ? reject(err) : resolve(stats!)))
|
||||||
|
|
||||||
|
if (stats.hasErrors()) {
|
||||||
|
const error = new Error('Nuxt build error')
|
||||||
|
error.stack = stats.toString('errors-only')
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
87
packages/rspack/src/utils/config.ts
Normal file
87
packages/rspack/src/utils/config.ts
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import { cloneDeep } from 'lodash-es'
|
||||||
|
import type { Configuration } from '@rspack/core'
|
||||||
|
import type { Nuxt } from '@nuxt/schema'
|
||||||
|
import { logger } from '@nuxt/kit'
|
||||||
|
|
||||||
|
export interface RspackConfigContext extends ReturnType<typeof createRspackConfigContext>{ }
|
||||||
|
|
||||||
|
type RspackConfigPreset = (ctx: RspackConfigContext, options?: object) => void
|
||||||
|
type RspackConfigPresetItem = RspackConfigPreset | [RspackConfigPreset, any]
|
||||||
|
|
||||||
|
export function createRspackConfigContext (nuxt: Nuxt) {
|
||||||
|
return {
|
||||||
|
nuxt,
|
||||||
|
options: nuxt.options,
|
||||||
|
config: {} as Configuration,
|
||||||
|
|
||||||
|
name: 'base',
|
||||||
|
isDev: nuxt.options.dev,
|
||||||
|
isServer: false,
|
||||||
|
isClient: false,
|
||||||
|
|
||||||
|
alias: {} as { [index: string]: string | false | string[] },
|
||||||
|
transpile: [] as RegExp[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyPresets (ctx: RspackConfigContext, presets: RspackConfigPresetItem | RspackConfigPresetItem[]) {
|
||||||
|
if (!Array.isArray(presets)) {
|
||||||
|
presets = [presets]
|
||||||
|
}
|
||||||
|
for (const preset of presets) {
|
||||||
|
if (Array.isArray(preset)) {
|
||||||
|
preset[0](ctx, preset[1])
|
||||||
|
} else {
|
||||||
|
preset(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fileName (ctx: RspackConfigContext, key: string) {
|
||||||
|
const { options } = ctx
|
||||||
|
|
||||||
|
let fileName = options.webpack.filenames[key as keyof typeof options.webpack.filenames] as ((ctx: RspackConfigContext) => string) | string
|
||||||
|
|
||||||
|
if (typeof fileName === 'function') {
|
||||||
|
fileName = fileName(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof fileName === 'string' && options.dev) {
|
||||||
|
const hash = /\[(chunkhash|contenthash|hash)(?::(\d+))?]/.exec(fileName)
|
||||||
|
if (hash) {
|
||||||
|
logger.warn(`Notice: Please do not use ${hash[1]} in dev mode to prevent memory leak`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fileName
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRspackConfig (ctx: RspackConfigContext): Configuration {
|
||||||
|
const { options, config } = ctx
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
const builder = {}
|
||||||
|
const loaders: any[] = []
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const { extend } = options.build
|
||||||
|
if (typeof extend === 'function') {
|
||||||
|
const extendedConfig = extend.call(
|
||||||
|
builder,
|
||||||
|
config,
|
||||||
|
{ loaders, ...ctx }
|
||||||
|
) || config
|
||||||
|
|
||||||
|
const pragma = /@|#/
|
||||||
|
const { devtool } = extendedConfig
|
||||||
|
if (typeof devtool === 'string' && pragma.test(devtool)) {
|
||||||
|
extendedConfig.devtool = devtool.replace(pragma, '')
|
||||||
|
logger.warn(`devtool has been normalized to ${extendedConfig.devtool} as webpack documented value`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return extendedConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone deep avoid leaking config between Client and Server
|
||||||
|
return cloneDeep(config)
|
||||||
|
}
|
24
packages/rspack/src/utils/mfs.ts
Normal file
24
packages/rspack/src/utils/mfs.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { join } from 'pathe'
|
||||||
|
import pify from 'pify'
|
||||||
|
import { Volume, createFsFromVolume } from 'memfs'
|
||||||
|
|
||||||
|
import type { IFs } from 'memfs'
|
||||||
|
|
||||||
|
export function createMFS () {
|
||||||
|
// Create a new volume
|
||||||
|
const fs = createFsFromVolume(new Volume())
|
||||||
|
|
||||||
|
// Clone to extend
|
||||||
|
const _fs: IFs & { join?(...paths: string[]): string } = { ...fs } as any
|
||||||
|
|
||||||
|
// fs.join method is (still) expected by @rspack/dev-middleware
|
||||||
|
// There might be differences with https://github.com/webpack/memory-fs/blob/master/lib/join.js
|
||||||
|
_fs.join = join
|
||||||
|
|
||||||
|
// Used by vue-renderer
|
||||||
|
_fs.exists = p => Promise.resolve(_fs.existsSync(p))
|
||||||
|
// @ts-ignore
|
||||||
|
_fs.readFile = pify(_fs.readFile)
|
||||||
|
|
||||||
|
return _fs as IFs & { join?(...paths: string[]): string }
|
||||||
|
}
|
84
packages/rspack/src/utils/postcss.ts
Normal file
84
packages/rspack/src/utils/postcss.ts
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import { createCommonJS } from 'mlly'
|
||||||
|
import { defaults, merge, cloneDeep } from 'lodash-es'
|
||||||
|
import { requireModule } from '@nuxt/kit'
|
||||||
|
import type { Nuxt } from '@nuxt/schema'
|
||||||
|
|
||||||
|
const isPureObject = (obj: unknown): obj is Object => obj !== null && !Array.isArray(obj) && typeof obj === 'object'
|
||||||
|
|
||||||
|
export const orderPresets = {
|
||||||
|
cssnanoLast (names: string[]) {
|
||||||
|
const nanoIndex = names.indexOf('cssnano')
|
||||||
|
if (nanoIndex !== names.length - 1) {
|
||||||
|
names.push(names.splice(nanoIndex, 1)[0])
|
||||||
|
}
|
||||||
|
return names
|
||||||
|
},
|
||||||
|
autoprefixerLast (names: string[]) {
|
||||||
|
const nanoIndex = names.indexOf('autoprefixer')
|
||||||
|
if (nanoIndex !== names.length - 1) {
|
||||||
|
names.push(names.splice(nanoIndex, 1)[0])
|
||||||
|
}
|
||||||
|
return names
|
||||||
|
},
|
||||||
|
autoprefixerAndCssnanoLast (names: string[]) {
|
||||||
|
return orderPresets.cssnanoLast(orderPresets.autoprefixerLast(names))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getPostcssConfig = (nuxt: Nuxt) => {
|
||||||
|
function defaultConfig () {
|
||||||
|
return {
|
||||||
|
sourceMap: nuxt.options.webpack.cssSourceMap,
|
||||||
|
plugins: nuxt.options.postcss.plugins,
|
||||||
|
// Array, String or Function
|
||||||
|
order: 'autoprefixerAndCssnanoLast'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortPlugins ({ plugins, order }: any) {
|
||||||
|
const names = Object.keys(plugins)
|
||||||
|
if (typeof order === 'string') {
|
||||||
|
order = orderPresets[order as keyof typeof orderPresets]
|
||||||
|
}
|
||||||
|
return typeof order === 'function' ? order(names, orderPresets) : (order || names)
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadPlugins (config: any) {
|
||||||
|
if (!isPureObject(config.plugins)) { return }
|
||||||
|
|
||||||
|
// Map postcss plugins into instances on object mode once
|
||||||
|
const cjs = createCommonJS(import.meta.url)
|
||||||
|
config.plugins = sortPlugins(config).map((pluginName: string) => {
|
||||||
|
const pluginFn = requireModule(pluginName, { paths: [cjs.__dirname] })
|
||||||
|
const pluginOptions = config.plugins[pluginName]
|
||||||
|
if (!pluginOptions || typeof pluginFn !== 'function') { return null }
|
||||||
|
return pluginFn(pluginOptions)
|
||||||
|
}).filter(Boolean)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!nuxt.options.webpack.postcss || !nuxt.options.postcss) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
let postcssOptions = cloneDeep(nuxt.options.postcss)
|
||||||
|
// Apply default plugins
|
||||||
|
if (isPureObject(postcssOptions)) {
|
||||||
|
if (Array.isArray(postcssOptions.plugins)) {
|
||||||
|
defaults(postcssOptions, defaultConfig())
|
||||||
|
} else {
|
||||||
|
// Keep the order of default plugins
|
||||||
|
postcssOptions = merge({}, defaultConfig(), postcssOptions)
|
||||||
|
loadPlugins(postcssOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-expect-error
|
||||||
|
delete nuxt.options.webpack.postcss.order
|
||||||
|
|
||||||
|
return {
|
||||||
|
// @ts-expect-error
|
||||||
|
sourceMap: nuxt.options.webpack.cssSourceMap,
|
||||||
|
...nuxt.options.webpack.postcss,
|
||||||
|
postcssOptions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
28
packages/rspack/src/virtual-modules.ts
Normal file
28
packages/rspack/src/virtual-modules.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { useNuxt } from '@nuxt/kit'
|
||||||
|
import VirtualModulesPlugin from 'webpack-virtual-modules'
|
||||||
|
|
||||||
|
export function registerVirtualModules () {
|
||||||
|
const nuxt = useNuxt()
|
||||||
|
|
||||||
|
// Initialize virtual modules instance
|
||||||
|
const virtualModules = new VirtualModulesPlugin(nuxt.vfs)
|
||||||
|
const writeFiles = () => {
|
||||||
|
console.log('writing files')
|
||||||
|
for (const filePath in nuxt.vfs) {
|
||||||
|
virtualModules.writeModule(filePath, nuxt.vfs[filePath])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Workaround to initialize virtual modules
|
||||||
|
nuxt.hook('rspack:compile', ({ compiler }) => {
|
||||||
|
writeFiles()
|
||||||
|
if (compiler.name === 'server') { writeFiles() }
|
||||||
|
})
|
||||||
|
// Update virtual modules when templates are updated
|
||||||
|
nuxt.hook('app:templatesGenerated', writeFiles)
|
||||||
|
|
||||||
|
nuxt.hook('rspack:config', configs => configs.forEach((config) => {
|
||||||
|
// Support virtual modules (input)
|
||||||
|
config.plugins!.push(virtualModules)
|
||||||
|
}))
|
||||||
|
}
|
@ -34,7 +34,6 @@ export default defineBuildConfig({
|
|||||||
'mini-css-extract-plugin',
|
'mini-css-extract-plugin',
|
||||||
'terser-webpack-plugin',
|
'terser-webpack-plugin',
|
||||||
'css-minimizer-webpack-plugin',
|
'css-minimizer-webpack-plugin',
|
||||||
'webpack-dev-middleware',
|
|
||||||
'h3',
|
'h3',
|
||||||
'webpack-hot-middleware',
|
'webpack-hot-middleware',
|
||||||
'postcss',
|
'postcss',
|
||||||
|
@ -1,3 +1,10 @@
|
|||||||
export default defineNuxtConfig({
|
export default defineNuxtConfig({
|
||||||
builder: 'rspack'
|
// TODO: Cannot create property 'global' on boolean 'false'
|
||||||
|
ssr: false,
|
||||||
|
builder: 'rspack',
|
||||||
|
app: {
|
||||||
|
head: {
|
||||||
|
link: [{ rel: 'stylesheet', href: 'https://unpkg.com/@picocss/pico@1.*/css/pico.min.css' }]
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
1414
pnpm-lock.yaml
1414
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user