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 { $fetch } from 'ofetch'
|
||||
// @ts-ignore
|
||||
@ -9,7 +8,8 @@ import '#build/css'
|
||||
// @ts-ignore
|
||||
import _plugins from '#build/plugins'
|
||||
// @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
|
||||
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
|
||||
}
|
||||
|
||||
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.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',
|
||||
'terser-webpack-plugin',
|
||||
'css-minimizer-webpack-plugin',
|
||||
'webpack-dev-middleware',
|
||||
'h3',
|
||||
'webpack-hot-middleware',
|
||||
'postcss',
|
||||
|
@ -1,3 +1,10 @@
|
||||
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