From 76fdc27437d37534cf157bf869a648e0d176b267 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Sat, 21 May 2022 13:38:33 +0800 Subject: [PATCH] feat!: migrate to ESM (#8178) --- .eslintrc.cjs | 6 + package.json | 1 + packages/plugin-vue-jsx/src/index.ts | 2 +- packages/plugin-vue/src/style.ts | 2 +- packages/vite/LICENSE.md | 2 +- packages/vite/bin/vite.js | 10 +- packages/vite/index.cjs | 33 ++ packages/vite/package.json | 27 +- .../{rollup.config.js => rollup.config.ts} | 350 +++++++++++------- packages/vite/scripts/patchTypes.ts | 8 +- packages/vite/scripts/tsconfig.json | 6 + .../vite/src/node/__tests__/build.spec.ts | 3 + .../vite/src/node/__tests__/config.spec.ts | 3 +- .../plugins/dynamicImportVar/parse.test.ts | 12 +- .../plugins/importGlob/fixture.test.ts | 3 + packages/vite/src/node/build.ts | 3 +- packages/vite/src/node/cli.ts | 5 +- packages/vite/src/node/config.ts | 127 +------ packages/vite/src/node/constants.ts | 18 +- packages/vite/src/node/index.ts | 10 +- packages/vite/src/node/plugins/css.ts | 16 +- .../vite/src/node/plugins/importMetaGlob.ts | 4 +- .../vite/src/node/plugins/splitVendorChunk.ts | 7 +- .../vite/src/node/plugins/ssrRequireHook.ts | 5 +- packages/vite/src/node/plugins/terser.ts | 10 +- packages/vite/src/node/publicUtils.ts | 13 + .../node/server/__tests__/search-root.spec.ts | 5 +- packages/vite/src/node/server/index.ts | 9 +- .../src/node/server/middlewares/static.ts | 4 +- packages/vite/src/node/server/openBrowser.ts | 5 +- .../vite/src/node/server/pluginContainer.ts | 9 +- .../ssr/__tests__/ssrModuleLoader.spec.ts | 6 +- packages/vite/src/node/ssr/ssrExternal.ts | 7 +- packages/vite/src/node/tsconfig.json | 2 +- packages/vite/src/node/utils.ts | 123 +++++- packages/vite/tsconfig.base.json | 1 + playground/vitestSetup.ts | 2 +- .../vue-sourcemap/__tests__/serve.spec.ts | 2 +- pnpm-lock.yaml | 47 +++ 39 files changed, 597 insertions(+), 311 deletions(-) create mode 100644 packages/vite/index.cjs rename packages/vite/{rollup.config.js => rollup.config.ts} (54%) create mode 100644 packages/vite/scripts/tsconfig.json create mode 100644 packages/vite/src/node/publicUtils.ts diff --git a/.eslintrc.cjs b/.eslintrc.cjs index f6db9c4b4cbc5c..961fe199582d87 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -148,6 +148,12 @@ module.exports = defineConfig({ rules: { '@typescript-eslint/triple-slash-reference': 'off' } + }, + { + files: 'packages/vite/**/*.*', + rules: { + 'no-restricted-globals': ['error', 'require', '__dirname', '__filename'] + } } ] }) diff --git a/package.json b/package.json index 9144ab4d6e9590..bcfa5acb5efdf0 100644 --- a/package.json +++ b/package.json @@ -77,6 +77,7 @@ "prompts": "^2.4.2", "rimraf": "^3.0.2", "rollup": "^2.72.1", + "rollup-plugin-esbuild": "^4.9.1", "semver": "^7.3.7", "simple-git-hooks": "^2.7.0", "sirv": "^2.0.2", diff --git a/packages/plugin-vue-jsx/src/index.ts b/packages/plugin-vue-jsx/src/index.ts index c96fe2964e83b0..302cb323849ad6 100644 --- a/packages/plugin-vue-jsx/src/index.ts +++ b/packages/plugin-vue-jsx/src/index.ts @@ -1,7 +1,7 @@ import { createHash } from 'crypto' import path from 'path' import type { types } from '@babel/core' -import babel from '@babel/core' +import * as babel from '@babel/core' import jsx from '@vue/babel-plugin-jsx' // @ts-expect-error missing type import importMeta from '@babel/plugin-syntax-import-meta' diff --git a/packages/plugin-vue/src/style.ts b/packages/plugin-vue/src/style.ts index aa124de17b8894..78fa4a94576222 100644 --- a/packages/plugin-vue/src/style.ts +++ b/packages/plugin-vue/src/style.ts @@ -51,7 +51,7 @@ export async function transformStyle( } const map = result.map - ? formatPostcssSourceMap( + ? await formatPostcssSourceMap( // version property of result.map is declared as string // but actually it is a number result.map as Omit as ExistingRawSourceMap, diff --git a/packages/vite/LICENSE.md b/packages/vite/LICENSE.md index 2b43883f1b2b67..e65a09826f0f4a 100644 --- a/packages/vite/LICENSE.md +++ b/packages/vite/LICENSE.md @@ -1648,7 +1648,7 @@ Repository: gulpjs/glob-parent ## http-proxy License: MIT -By: Charlie Robbins +By: Charlie Robbins, jcrugzz Repository: https://github.com/http-party/node-http-proxy.git > node-http-proxy diff --git a/packages/vite/bin/vite.js b/packages/vite/bin/vite.js index 75eb0ac5452653..143bb96a5d88bb 100755 --- a/packages/vite/bin/vite.js +++ b/packages/vite/bin/vite.js @@ -1,10 +1,10 @@ #!/usr/bin/env node -const { performance } = require('perf_hooks') +import { performance } from 'perf_hooks' -if (!__dirname.includes('node_modules')) { +if (!import.meta.url.includes('node_modules')) { try { // only available as dev dependency - require('source-map-support').install() + await import('source-map-support').then((r) => r.default.install()) } catch (e) {} } @@ -41,7 +41,7 @@ if (debugIndex > 0) { } function start() { - require('../dist/node/cli') + return import('../dist/node/cli.js') } if (profileIndex > 0) { @@ -50,7 +50,7 @@ if (profileIndex > 0) { if (next && !next.startsWith('-')) { process.argv.splice(profileIndex, 1) } - const inspector = require('inspector') + const inspector = await import('inspector').then((r) => r.default) const session = (global.__vite_profile_session = new inspector.Session()) session.connect() session.post('Profiler.enable', () => { diff --git a/packages/vite/index.cjs b/packages/vite/index.cjs new file mode 100644 index 00000000000000..1c8aba26da6565 --- /dev/null +++ b/packages/vite/index.cjs @@ -0,0 +1,33 @@ +/* eslint-disable no-restricted-globals */ + +// type utils +module.exports.defineConfig = (config) => config + +// proxy cjs utils (sync functions) +Object.assign(module.exports, require('./dist/node-cjs/publicUtils.cjs')) + +// async functions, can be redirect from ESM build +const asyncFunctions = [ + 'build', + 'createServer', + 'preview', + 'transformWithEsbuild', + 'resolveConfig', + 'optimizeDeps', + 'formatPostcssSourceMap', + 'loadConfigFromFile' +] +asyncFunctions.forEach((name) => { + module.exports[name] = (...args) => + import('./dist/node/index.js').then((i) => i[name](...args)) +}) + +// some sync functions are marked not supported due to their complexity and uncommon usage +const unsupportedCJS = ['resolvePackageEntry', 'resolvePackageData'] +unsupportedCJS.forEach((name) => { + module.exports[name] = () => { + throw new Error( + `"${name}" is not supported in CJS build of Vite 3.\nPlease use ESM or dynamic imports \`const { ${name} } = await import('vite')\`.` + ) + } +}) diff --git a/packages/vite/package.json b/packages/vite/package.json index a6262470645e76..d3a55fc3ae1e58 100644 --- a/packages/vite/package.json +++ b/packages/vite/package.json @@ -1,18 +1,34 @@ { "name": "vite", "version": "3.0.0-alpha.1", + "type": "module", "license": "MIT", "author": "Evan You", "description": "Native-ESM powered web dev build tool", "bin": { "vite": "bin/vite.js" }, - "main": "dist/node/index.js", - "types": "dist/node/index.d.ts", + "main": "./dist/node/index.js", + "module": "./dist/node/index.js", + "types": "./dist/node/index.d.ts", + "exports": { + ".": { + "types": "./dist/node/index.d.ts", + "import": "./dist/node/index.js", + "require": "./index.cjs" + }, + "./client": { + "types": "./client.d.ts" + }, + "./terser": { + "require": "./dist/node-cjs/terser.cjs" + } + }, "files": [ "bin", "dist", "client.d.ts", + "index.cjs", "src/client", "types" ], @@ -29,12 +45,12 @@ }, "homepage": "https://github.com/vitejs/vite/tree/main/#readme", "scripts": { - "dev": "rimraf dist && rollup -c -w", + "dev": "rimraf dist && pnpm run build-bundle -w", "build": "rimraf dist && run-s build-bundle build-types", - "build-bundle": "rollup -c", + "build-bundle": "rollup --config rollup.config.ts --configPlugin esbuild", "build-types": "run-s build-temp-types patch-types roll-types", "build-temp-types": "tsc --emitDeclarationOnly --outDir temp/node -p src/node", - "patch-types": "ts-node scripts/patchTypes.ts", + "patch-types": "esno scripts/patchTypes.ts", "roll-types": "api-extractor run && rimraf temp", "lint": "eslint --ext .ts src/**", "format": "prettier --write --parser typescript \"src/**/*.ts\"", @@ -75,6 +91,7 @@ "dotenv": "^14.3.2", "dotenv-expand": "^5.1.0", "es-module-lexer": "^0.10.5", + "esno": "^0.16.3", "estree-walker": "^2.0.2", "etag": "^1.8.1", "fast-glob": "^3.2.11", diff --git a/packages/vite/rollup.config.js b/packages/vite/rollup.config.ts similarity index 54% rename from packages/vite/rollup.config.js rename to packages/vite/rollup.config.ts index 450217f91d8a80..17f2f08c3c8469 100644 --- a/packages/vite/rollup.config.js +++ b/packages/vite/rollup.config.ts @@ -1,4 +1,4 @@ -// @ts-check +/* eslint-disable no-restricted-globals */ import fs from 'fs' import path from 'path' import nodeResolve from '@rollup/plugin-node-resolve' @@ -11,15 +11,16 @@ import MagicString from 'magic-string' import colors from 'picocolors' import fg from 'fast-glob' import { sync as resolve } from 'resolve' +import type { Plugin } from 'rollup' +import { defineConfig } from 'rollup' +import pkg from './package.json' -/** - * @type { import('rollup').RollupOptions } - */ -const envConfig = { +const envConfig = defineConfig({ input: path.resolve(__dirname, 'src/client/env.ts'), plugins: [ typescript({ target: 'es2020', + module: 'esnext', include: ['src/client/env.ts'], baseUrl: path.resolve(__dirname, 'src/env'), paths: { @@ -31,12 +32,9 @@ const envConfig = { file: path.resolve(__dirname, 'dist/client', 'env.mjs'), sourcemap: true } -} +}) -/** - * @type { import('rollup').RollupOptions } - */ -const clientConfig = { +const clientConfig = defineConfig({ input: path.resolve(__dirname, 'src/client/client.ts'), external: ['./env', '@vite/env'], plugins: [ @@ -53,12 +51,9 @@ const clientConfig = { file: path.resolve(__dirname, 'dist/client', 'client.mjs'), sourcemap: true } -} +}) -/** - * @type { import('rollup').RollupOptions } - */ -const sharedNodeOptions = { +const sharedNodeOptions = defineConfig({ treeshake: { moduleSideEffects: 'no-external', propertyReadSideEffects: false, @@ -69,7 +64,7 @@ const sharedNodeOptions = { entryFileNames: `node/[name].js`, chunkFileNames: 'node/chunks/dep-[hash].js', exports: 'named', - format: 'cjs', + format: 'esm', externalLiveBindings: false, freeze: false }, @@ -87,22 +82,98 @@ const sharedNodeOptions = { } warn(warning) } +}) + +function createNodePlugins(isProduction: boolean, sourceMap = true): Plugin[] { + return [ + alias({ + // packages with "module" field that doesn't play well with cjs bundles + entries: { + '@vue/compiler-dom': require.resolve( + '@vue/compiler-dom/dist/compiler-dom.cjs.js' + ) + } + }), + nodeResolve({ preferBuiltins: true }), + typescript({ + tsconfig: 'src/node/tsconfig.json', + module: 'esnext', + target: 'es2020', + include: ['src/**/*.ts', 'types/**'], + exclude: ['src/**/__tests__/**'], + esModuleInterop: true, + sourceMap, + // in production we use api-extractor for dts generation + // in development we need to rely on the rollup ts plugin + ...(isProduction + ? { + declaration: false, + sourceMap: false + } + : { + declaration: true, + declarationDir: path.resolve(__dirname, 'dist/node') + }) + }), + + // Some deps have try...catch require of optional deps, but rollup will + // generate code that force require them upfront for side effects. + // Shim them with eval() so rollup can skip these calls. + isProduction && + shimDepsPlugin({ + 'plugins/terser.ts': { + src: `require.resolve('terser'`, + replacement: `require.resolve('vite/terser'` + }, + // chokidar -> fsevents + 'fsevents-handler.js': { + src: `require('fsevents')`, + replacement: `__require('fsevents')` + }, + // cac re-assigns module.exports even in its mjs dist + 'cac/dist/index.mjs': { + src: `if (typeof module !== "undefined") {`, + replacement: `if (false) {` + }, + // postcss-import -> sugarss + 'process-content.js': { + src: 'require("sugarss")', + replacement: `__require('sugarss')` + }, + 'lilconfig/dist/index.js': { + pattern: /: require,/g, + replacement: `: __require,` + }, + // postcss-load-config calls require after register ts-node + 'postcss-load-config/src/index.js': { + src: `require(configFile)`, + replacement: `__require(configFile)` + }, + // @rollup/plugin-commonjs uses incorrect esm + '@rollup/plugin-commonjs/dist/index.es.js': { + src: `import { sync } from 'resolve';`, + replacement: `import __resolve from 'resolve';const sync = __resolve.sync;` + } + }), + commonjs({ + extensions: ['.js'], + // Optional peer deps of ws. Native deps that are mostly for performance. + // Since ws is not that perf critical for us, just ignore these deps. + ignore: ['bufferutil', 'utf-8-validate'] + }), + json(), + isProduction && licensePlugin(), + cjsPatchPlugin() + ] } -/** - * - * @param {boolean} isProduction - * @returns {import('rollup').RollupOptions} - */ -const createNodeConfig = (isProduction) => { - /** - * @type { import('rollup').RollupOptions } - */ - const nodeConfig = { +function createNodeConfig(isProduction: boolean) { + return defineConfig({ ...sharedNodeOptions, input: { index: path.resolve(__dirname, 'src/node/index.ts'), - cli: path.resolve(__dirname, 'src/node/cli.ts') + cli: path.resolve(__dirname, 'src/node/cli.ts'), + constants: path.resolve(__dirname, 'src/node/constants.ts') }, output: { ...sharedNodeOptions.output, @@ -110,113 +181,82 @@ const createNodeConfig = (isProduction) => { }, external: [ 'fsevents', - ...Object.keys(require('./package.json').dependencies), - ...(isProduction - ? [] - : Object.keys(require('./package.json').devDependencies)) + ...Object.keys(pkg.dependencies), + ...(isProduction ? [] : Object.keys(pkg.devDependencies)) ], - plugins: [ - alias({ - // packages with "module" field that doesn't play well with cjs bundles - entries: { - '@vue/compiler-dom': require.resolve( - '@vue/compiler-dom/dist/compiler-dom.cjs.js' - ) - } - }), - nodeResolve({ preferBuiltins: true }), - typescript({ - tsconfig: 'src/node/tsconfig.json', - module: 'esnext', - target: 'es2020', - include: ['src/**/*.ts', 'types/**'], - exclude: ['src/**/__tests__/**'], - esModuleInterop: true, - // in production we use api-extractor for dts generation - // in development we need to rely on the rollup ts plugin - ...(isProduction - ? { - declaration: false, - sourceMap: false - } - : { - declaration: true, - declarationDir: path.resolve(__dirname, 'dist/node') - }) - }), - // Some deps have try...catch require of optional deps, but rollup will - // generate code that force require them upfront for side effects. - // Shim them with eval() so rollup can skip these calls. - isProduction && - shimDepsPlugin({ - 'plugins/terser.ts': { - src: `require.resolve('terser'`, - replacement: `require.resolve('vite/dist/node/terser'` - }, - // chokidar -> fsevents - 'fsevents-handler.js': { - src: `require('fsevents')`, - replacement: `eval('require')('fsevents')` - }, - // cac re-assigns module.exports even in its mjs dist - 'cac/dist/index.mjs': { - src: `if (typeof module !== "undefined") {`, - replacement: `if (false) {` - }, - // postcss-import -> sugarss - 'process-content.js': { - src: 'require("sugarss")', - replacement: `eval('require')('sugarss')` - }, - 'lilconfig/dist/index.js': { - pattern: /: require,/g, - replacement: `: eval('require'),` - }, - // postcss-load-config calls require after register ts-node - 'postcss-load-config/src/index.js': { - src: `require(configFile)`, - replacement: `eval('require')(configFile)` - } - }), - commonjs({ - extensions: ['.js'], - // Optional peer deps of ws. Native deps that are mostly for performance. - // Since ws is not that perf critical for us, just ignore these deps. - ignore: ['bufferutil', 'utf-8-validate'] - }), - json(), - isProduction && licensePlugin() - ] - } - - return nodeConfig + plugins: createNodePlugins(isProduction) + }) } /** * Terser needs to be run inside a worker, so it cannot be part of the main * bundle. We produce a separate bundle for it and shims plugin/terser.ts to * use the production path during build. - * - * @type { import('rollup').RollupOptions } */ -const terserConfig = { +const terserConfig = defineConfig({ ...sharedNodeOptions, output: { ...sharedNodeOptions.output, + entryFileNames: `node-cjs/[name].cjs`, exports: 'default', + format: 'cjs', sourcemap: false }, input: { + // eslint-disable-next-line node/no-restricted-require terser: require.resolve('terser') }, plugins: [nodeResolve(), commonjs()] +}) + +function createCjsConfig(isProduction: boolean) { + return defineConfig({ + ...sharedNodeOptions, + input: { + publicUtils: path.resolve(__dirname, 'src/node/publicUtils.ts') + }, + output: { + dir: path.resolve(__dirname, 'dist'), + entryFileNames: `node-cjs/[name].cjs`, + chunkFileNames: 'node-cjs/chunks/dep-[hash].js', + exports: 'named', + format: 'cjs', + externalLiveBindings: false, + freeze: false, + sourcemap: false + }, + external: [ + 'fsevents', + ...Object.keys(pkg.dependencies), + ...(isProduction ? [] : Object.keys(pkg.devDependencies)) + ], + plugins: [...createNodePlugins(false, false), bundleSizeLimit(50)] + }) } -/** - * @type { (deps: Record) => import('rollup').Plugin } - */ -function shimDepsPlugin(deps) { - const transformed = {} +export default (commandLineArgs: any) => { + const isDev = commandLineArgs.watch + const isProduction = !isDev + + return defineConfig([ + envConfig, + clientConfig, + createNodeConfig(isProduction), + createCjsConfig(isProduction), + ...(isProduction ? [terserConfig] : []) + ]) +} + +// #region ======== Plugins ======== + +interface ShimOptions { + src?: string + replacement: string + pattern?: RegExp +} + +function shimDepsPlugin(deps: Record): Plugin { + const transformed: Record = {} return { name: 'shim-deps', @@ -317,19 +357,19 @@ function licensePlugin() { text += `License: ${license}\n` } const names = new Set() - if (author && author.name) { - names.add(author.name) - } - for (const person of maintainers.concat(contributors)) { - if (person && person.name) { - names.add(person.name) + for (const person of [author, ...maintainers, ...contributors]) { + const name = typeof person === 'string' ? person : person?.name + if (name) { + names.add(name) } } if (names.size > 0) { text += `By: ${Array.from(names).join(', ')}\n` } if (repository) { - text += `Repository: ${repository.url || repository}\n` + text += `Repository: ${ + typeof repository === 'string' ? repository : repository.url + }\n` } if (!licenseText) { try { @@ -384,14 +424,64 @@ function licensePlugin() { }) } -export default (commandLineArgs) => { - const isDev = commandLineArgs.watch - const isProduction = !isDev +/** + * Inject CJS Context for each deps chunk + */ +function cjsPatchPlugin(): Plugin { + const cjsPatch = ` +import { fileURLToPath as __cjs_fileURLToPath } from 'url'; +import { dirname as __cjs_dirname } from 'path'; +import { createRequire as __cjs_createRequire } from 'module'; - return [ - envConfig, - clientConfig, - createNodeConfig(isProduction), - ...(isProduction ? [terserConfig] : []) - ] +const __filename = __cjs_fileURLToPath(import.meta.url); +const __dirname = __cjs_dirname(__filename); +const require = __cjs_createRequire(import.meta.url); +const __require = require; +`.trimStart() + + return { + name: 'cjs-chunk-patch', + renderChunk(code, chunk) { + if (!chunk.fileName.includes('chunks/dep-')) return + + const match = code.match(/^(?:import[\s\S]*?;\s*)+/) + const index = match ? match.index + match[0].length : 0 + const s = new MagicString(code) + // inject after the last `import` + s.appendRight(index, cjsPatch) + console.log('patched cjs context: ' + chunk.fileName) + + return { + code: s.toString(), + map: s.generateMap() + } + } + } +} + +/** + * Guard the bundle size + * + * @param limit size in KB + */ +function bundleSizeLimit(limit: number): Plugin { + return { + name: 'bundle-limit', + generateBundle(options, bundle) { + const size = Buffer.byteLength( + Object.values(bundle) + .map((i) => ('code' in i ? i.code : '')) + .join(''), + 'utf-8' + ) + const kb = size / 1024 + if (kb > limit) { + throw new Error( + `Bundle size exceeded ${limit}kb, current size is ${kb.toFixed(2)}kb.` + ) + } + } + } } + +// #endregion diff --git a/packages/vite/scripts/patchTypes.ts b/packages/vite/scripts/patchTypes.ts index ec5ade0224391c..38c6300ea1954b 100644 --- a/packages/vite/scripts/patchTypes.ts +++ b/packages/vite/scripts/patchTypes.ts @@ -1,10 +1,14 @@ +import { readFileSync, readdirSync, statSync, writeFileSync } from 'fs' +import { dirname, relative, resolve } from 'path' +import { fileURLToPath } from 'url' import type { ParseResult } from '@babel/parser' import { parse } from '@babel/parser' import type { File } from '@babel/types' import colors from 'picocolors' -import { readdirSync, readFileSync, statSync, writeFileSync } from 'fs' import MagicString from 'magic-string' -import { dirname, relative, resolve } from 'path' + +// @ts-ignore +const __dirname = resolve(fileURLToPath(import.meta.url), '..') const tempDir = resolve(__dirname, '../temp/node') const typesDir = resolve(__dirname, '../types') diff --git a/packages/vite/scripts/tsconfig.json b/packages/vite/scripts/tsconfig.json new file mode 100644 index 00000000000000..af11a209690527 --- /dev/null +++ b/packages/vite/scripts/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../tsconfig.base.json", + "compilerOptions": { + "module": "esnext" + } +} diff --git a/packages/vite/src/node/__tests__/build.spec.ts b/packages/vite/src/node/__tests__/build.spec.ts index 97d823ff648dc7..25423bfb16e99b 100644 --- a/packages/vite/src/node/__tests__/build.spec.ts +++ b/packages/vite/src/node/__tests__/build.spec.ts @@ -1,8 +1,11 @@ import { resolve } from 'path' +import { fileURLToPath } from 'url' import { describe, expect, test } from 'vitest' import type { LibraryFormats, LibraryOptions } from '../build' import { resolveLibFilename } from '../build' +const __dirname = resolve(fileURLToPath(import.meta.url), '..') + type FormatsToFileNames = [LibraryFormats, string][] const baseLibOptions: LibraryOptions = { fileName: 'my-lib', diff --git a/packages/vite/src/node/__tests__/config.spec.ts b/packages/vite/src/node/__tests__/config.spec.ts index 4eaeae2822a1e3..77314a896920e7 100644 --- a/packages/vite/src/node/__tests__/config.spec.ts +++ b/packages/vite/src/node/__tests__/config.spec.ts @@ -1,7 +1,8 @@ import { describe, expect, test } from 'vitest' import type { InlineConfig } from '..' import type { UserConfig, UserConfigExport } from '../config' -import { mergeConfig, resolveConfig, resolveEnvPrefix } from '../config' +import { resolveConfig, resolveEnvPrefix } from '../config' +import { mergeConfig } from '../publicUtils' describe('mergeConfig', () => { test('handles configs with different alias schemas', () => { diff --git a/packages/vite/src/node/__tests__/plugins/dynamicImportVar/parse.test.ts b/packages/vite/src/node/__tests__/plugins/dynamicImportVar/parse.test.ts index a0017d50ae6cd2..5ad8dac1dde3c5 100644 --- a/packages/vite/src/node/__tests__/plugins/dynamicImportVar/parse.test.ts +++ b/packages/vite/src/node/__tests__/plugins/dynamicImportVar/parse.test.ts @@ -1,13 +1,15 @@ import { resolve } from 'path' +import { fileURLToPath } from 'url' import { describe, expect, it } from 'vitest' import { transformDynamicImport } from '../../../plugins/dynamicImportVars' +const __dirname = resolve(fileURLToPath(import.meta.url), '..') + async function run(input: string) { - const { glob, rawPattern } = await transformDynamicImport( - input, - resolve(__dirname, 'index.js'), - (id) => id.replace('@', resolve(__dirname, './mods/')) - ) + const { glob, rawPattern } = + (await transformDynamicImport(input, resolve(__dirname, 'index.js'), (id) => + id.replace('@', resolve(__dirname, './mods/')) + )) || {} return `__variableDynamicImportRuntimeHelper(${glob}, \`${rawPattern}\`)` } diff --git a/packages/vite/src/node/__tests__/plugins/importGlob/fixture.test.ts b/packages/vite/src/node/__tests__/plugins/importGlob/fixture.test.ts index 985263d91db85a..1de3d5e32dbd15 100644 --- a/packages/vite/src/node/__tests__/plugins/importGlob/fixture.test.ts +++ b/packages/vite/src/node/__tests__/plugins/importGlob/fixture.test.ts @@ -1,9 +1,12 @@ import { resolve } from 'path' import { promises as fs } from 'fs' +import { fileURLToPath } from 'url' import { describe, expect, it } from 'vitest' import { transformGlobImport } from '../../../plugins/importMetaGlob' import { transformWithEsbuild } from '../../../plugins/esbuild' +const __dirname = resolve(fileURLToPath(import.meta.url), '..') + describe('fixture', async () => { const resolveId = (id: string) => id const root = resolve(__dirname, '..') diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index cdfc1540bda5cc..512f99732ffe8c 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -40,6 +40,7 @@ import { loadFallbackPlugin } from './plugins/loadFallback' import type { PackageData } from './packages' import { watchPackageDataPlugin } from './packages' import { ensureWatchPlugin } from './plugins/ensureWatch' +import { VERSION } from './constants' export interface BuildOptions { /** @@ -339,7 +340,7 @@ async function doBuild( config.logger.info( colors.cyan( - `vite v${require('vite/package.json').version} ${colors.green( + `vite v${VERSION} ${colors.green( `building ${ssr ? `SSR bundle ` : ``}for ${config.mode}...` )}` ) diff --git a/packages/vite/src/node/cli.ts b/packages/vite/src/node/cli.ts index 9ee807f505e016..00f68bb5ef60b7 100644 --- a/packages/vite/src/node/cli.ts +++ b/packages/vite/src/node/cli.ts @@ -5,6 +5,7 @@ import type { BuildOptions } from './build' import type { ServerOptions } from './server' import type { LogLevel } from './logger' import { createLogger } from './logger' +import { VERSION } from './constants' import { resolveConfig } from '.' const cli = cac('vite') @@ -97,7 +98,7 @@ cli const info = server.config.logger.info info( - colors.cyan(`\n vite v${require('vite/package.json').version}`) + + colors.cyan(`\n vite v${VERSION}`) + colors.green(` dev server running at:\n`), { clear: !server.config.logger.hasWarned @@ -257,6 +258,6 @@ cli ) cli.help() -cli.version(require('../../package.json').version) +cli.version(VERSION) cli.parse() diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index 02ce73c99a58c8..174fa0dd40f3d0 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -2,6 +2,7 @@ import fs from 'fs' import path from 'path' import { parse as parseUrl, pathToFileURL } from 'url' import { performance } from 'perf_hooks' +import { createRequire } from 'module' import colors from 'picocolors' import dotenv from 'dotenv' import dotenvExpand from 'dotenv-expand' @@ -25,6 +26,9 @@ import { isExternalUrl, isObject, lookupFile, + mergeAlias, + mergeConfig, + normalizeAlias, normalizePath } from './utils' import { resolvePlugins } from './plugins' @@ -616,118 +620,6 @@ function resolveBaseUrl( return base } -function mergeConfigRecursively( - defaults: Record, - overrides: Record, - rootPath: string -) { - const merged: Record = { ...defaults } - for (const key in overrides) { - const value = overrides[key] - if (value == null) { - continue - } - - const existing = merged[key] - - if (existing == null) { - merged[key] = value - continue - } - - // fields that require special handling - if (key === 'alias' && (rootPath === 'resolve' || rootPath === '')) { - merged[key] = mergeAlias(existing, value) - continue - } else if (key === 'assetsInclude' && rootPath === '') { - merged[key] = [].concat(existing, value) - continue - } else if ( - key === 'noExternal' && - rootPath === 'ssr' && - (existing === true || value === true) - ) { - merged[key] = true - continue - } - - if (Array.isArray(existing) || Array.isArray(value)) { - merged[key] = [...arraify(existing ?? []), ...arraify(value ?? [])] - continue - } - if (isObject(existing) && isObject(value)) { - merged[key] = mergeConfigRecursively( - existing, - value, - rootPath ? `${rootPath}.${key}` : key - ) - continue - } - - merged[key] = value - } - return merged -} - -export function mergeConfig( - defaults: Record, - overrides: Record, - isRoot = true -): Record { - return mergeConfigRecursively(defaults, overrides, isRoot ? '' : '.') -} - -function mergeAlias( - a?: AliasOptions, - b?: AliasOptions -): AliasOptions | undefined { - if (!a) return b - if (!b) return a - if (isObject(a) && isObject(b)) { - return { ...a, ...b } - } - // the order is flipped because the alias is resolved from top-down, - // where the later should have higher priority - return [...normalizeAlias(b), ...normalizeAlias(a)] -} - -function normalizeAlias(o: AliasOptions = []): Alias[] { - return Array.isArray(o) - ? o.map(normalizeSingleAlias) - : Object.keys(o).map((find) => - normalizeSingleAlias({ - find, - replacement: (o as any)[find] - }) - ) -} - -// https://github.com/vitejs/vite/issues/1363 -// work around https://github.com/rollup/plugins/issues/759 -function normalizeSingleAlias({ - find, - replacement, - customResolver -}: Alias): Alias { - if ( - typeof find === 'string' && - find.endsWith('/') && - replacement.endsWith('/') - ) { - find = find.slice(0, find.length - 1) - replacement = replacement.slice(0, replacement.length - 1) - } - - const alias: Alias = { - find, - replacement - } - if (customResolver) { - alias.customResolver = customResolver - } - return alias -} - export function sortUserPlugins( plugins: (Plugin | Plugin[])[] | undefined ): [Plugin[], Plugin[], Plugin[]] { @@ -934,14 +826,15 @@ interface NodeModuleWithCompile extends NodeModule { _compile(code: string, filename: string): any } +const _require = createRequire(import.meta.url) async function loadConfigFromBundledFile( fileName: string, bundledCode: string ): Promise { const extension = path.extname(fileName) const realFileName = fs.realpathSync(fileName) - const defaultLoader = require.extensions[extension]! - require.extensions[extension] = (module: NodeModule, filename: string) => { + const defaultLoader = _require.extensions[extension]! + _require.extensions[extension] = (module: NodeModule, filename: string) => { if (filename === realFileName) { ;(module as NodeModuleWithCompile)._compile(bundledCode, filename) } else { @@ -949,10 +842,10 @@ async function loadConfigFromBundledFile( } } // clear cache in case of server restart - delete require.cache[require.resolve(fileName)] - const raw = require(fileName) + delete _require.cache[_require.resolve(fileName)] + const raw = _require(fileName) const config = raw.__esModule ? raw.default : raw - require.extensions[extension] = defaultLoader + _require.extensions[extension] = defaultLoader return config } diff --git a/packages/vite/src/node/constants.ts b/packages/vite/src/node/constants.ts index 8f673c72f30fba..4e5d3da96f50da 100644 --- a/packages/vite/src/node/constants.ts +++ b/packages/vite/src/node/constants.ts @@ -1,4 +1,9 @@ -import path from 'path' +import path, { resolve } from 'path' +import { fileURLToPath } from 'url' +// @ts-expect-error +import { version } from '../../package.json' + +export const VERSION = version as string export const DEFAULT_MAIN_FIELDS = [ 'module', @@ -46,10 +51,13 @@ export const NULL_BYTE_PLACEHOLDER = `__x00__` export const CLIENT_PUBLIC_PATH = `/@vite/client` export const ENV_PUBLIC_PATH = `/@vite/env` -// eslint-disable-next-line node/no-missing-require -export const CLIENT_ENTRY = require.resolve('vite/dist/client/client.mjs') -// eslint-disable-next-line node/no-missing-require -export const ENV_ENTRY = require.resolve('vite/dist/client/env.mjs') +export const VITE_PACKAGE_DIR = resolve( + fileURLToPath(import.meta.url), + '../../..' +) + +export const CLIENT_ENTRY = resolve(VITE_PACKAGE_DIR, 'dist/client/client.mjs') +export const ENV_ENTRY = resolve(VITE_PACKAGE_DIR, 'dist/client/env.mjs') export const CLIENT_DIR = path.dirname(CLIENT_ENTRY) // ** READ THIS ** before editing `KNOWN_ASSET_TYPES`. diff --git a/packages/vite/src/node/index.ts b/packages/vite/src/node/index.ts index 0d401363b8a3b6..8e9cafd7d943a1 100644 --- a/packages/vite/src/node/index.ts +++ b/packages/vite/src/node/index.ts @@ -1,19 +1,13 @@ export * from './config' -export { createServer, searchForWorkspaceRoot } from './server' +export { createServer } from './server' export { preview } from './preview' export { build } from './build' export { optimizeDeps } from './optimizer' -export { send } from './server/send' -export { createLogger } from './logger' export { formatPostcssSourceMap } from './plugins/css' export { transformWithEsbuild } from './plugins/esbuild' export { resolvePackageEntry } from './plugins/resolve' -export { - splitVendorChunkPlugin, - splitVendorChunk -} from './plugins/splitVendorChunk' export { resolvePackageData } from './packages' -export { normalizePath } from './utils' +export * from './publicUtils' // additional types export type { CorsOptions, CorsOrigin, CommonServerOptions } from './http' diff --git a/packages/vite/src/node/plugins/css.ts b/packages/vite/src/node/plugins/css.ts index eeafc46feafee0..6e37d769f5beed 100644 --- a/packages/vite/src/node/plugins/css.ts +++ b/packages/vite/src/node/plugins/css.ts @@ -1,5 +1,6 @@ import fs from 'fs' import path from 'path' +import { createRequire } from 'module' import glob from 'fast-glob' import postcssrc from 'postcss-load-config' import type { @@ -888,7 +889,7 @@ async function compileCSS( const rawPostcssMap = postcssResult.map.toJSON() - const postcssMap = formatPostcssSourceMap( + const postcssMap = await formatPostcssSourceMap( // version property of rawPostcssMap is declared as string // but actually it is a number rawPostcssMap as Omit as ExistingRawSourceMap, @@ -904,10 +905,10 @@ async function compileCSS( } } -export function formatPostcssSourceMap( +export async function formatPostcssSourceMap( rawMap: ExistingRawSourceMap, file: string -): ExistingRawSourceMap { +): Promise { const inputFileDir = path.dirname(file) const sources = rawMap.sources.map((source) => { @@ -1278,6 +1279,9 @@ export interface StylePreprocessorResults { const loadedPreprocessors: Partial> = {} +// TODO: use dynamic import +const _require = createRequire(import.meta.url) + function loadPreprocessor(lang: PreprocessLang.scss, root: string): typeof Sass function loadPreprocessor(lang: PreprocessLang.sass, root: string): typeof Sass function loadPreprocessor(lang: PreprocessLang.less, root: string): typeof Less @@ -1292,9 +1296,9 @@ function loadPreprocessor(lang: PreprocessLang, root: string): any { try { // Search for the preprocessor in the root directory first, and fall back // to the default require paths. - const fallbackPaths = require.resolve.paths?.(lang) || [] - const resolved = require.resolve(lang, { paths: [root, ...fallbackPaths] }) - return (loadedPreprocessors[lang] = require(resolved)) + const fallbackPaths = _require.resolve.paths?.(lang) || [] + const resolved = _require.resolve(lang, { paths: [root, ...fallbackPaths] }) + return (loadedPreprocessors[lang] = _require(resolved)) } catch (e) { if (e.code === 'MODULE_NOT_FOUND') { throw new Error( diff --git a/packages/vite/src/node/plugins/importMetaGlob.ts b/packages/vite/src/node/plugins/importMetaGlob.ts index 5564bedefb8eac..9348402dae47e3 100644 --- a/packages/vite/src/node/plugins/importMetaGlob.ts +++ b/packages/vite/src/node/plugins/importMetaGlob.ts @@ -1,5 +1,5 @@ import { isAbsolute, posix } from 'path' -import { isMatch, scan } from 'micromatch' +import micromatch from 'micromatch' import { stripLiteral } from 'strip-literal' import type { ArrayExpression, @@ -20,6 +20,8 @@ import type { ResolvedConfig } from '../config' import { normalizePath, slash } from '../utils' import { isCSSRequest } from './css' +const { isMatch, scan } = micromatch + export interface ParsedImportGlob { match: RegExpMatchArray index: number diff --git a/packages/vite/src/node/plugins/splitVendorChunk.ts b/packages/vite/src/node/plugins/splitVendorChunk.ts index 7aa389da8377e2..1ce53eaeb5a259 100644 --- a/packages/vite/src/node/plugins/splitVendorChunk.ts +++ b/packages/vite/src/node/plugins/splitVendorChunk.ts @@ -6,7 +6,12 @@ import type { } from 'rollup' import type { UserConfig } from '../../node' import type { Plugin } from '../plugin' -import { isCSSRequest } from './css' + +// This file will be built for both ESM and CJS. Avoid relying on other modules as possible. +const cssLangs = `\\.(css|less|sass|scss|styl|stylus|pcss|postcss)($|\\?)` +const cssLangRE = new RegExp(cssLangs) +export const isCSSRequest = (request: string): boolean => + cssLangRE.test(request) // Use splitVendorChunkPlugin() to get the same manualChunks strategy as Vite 2.7 // We don't recommend using this strategy as a general solution moving forward diff --git a/packages/vite/src/node/plugins/ssrRequireHook.ts b/packages/vite/src/node/plugins/ssrRequireHook.ts index 0873e44245ab6a..5dbeb77180cb36 100644 --- a/packages/vite/src/node/plugins/ssrRequireHook.ts +++ b/packages/vite/src/node/plugins/ssrRequireHook.ts @@ -1,3 +1,4 @@ +import { createRequire } from 'module' import MagicString from 'magic-string' import type { ResolvedConfig } from '..' import type { Plugin } from '../plugin' @@ -49,6 +50,7 @@ type NodeResolveFilename = ( /** Respect the `resolve.dedupe` option in production SSR. */ function dedupeRequire(dedupe: string[]) { + // eslint-disable-next-line no-restricted-globals const Module = require('module') as { _resolveFilename: NodeResolveFilename } const resolveFilename = Module._resolveFilename Module._resolveFilename = function (request, parent, isMain, options) { @@ -64,10 +66,11 @@ function dedupeRequire(dedupe: string[]) { } } +const _require = createRequire(import.meta.url) export function hookNodeResolve( getResolver: (resolveFilename: NodeResolveFilename) => NodeResolveFilename ): () => void { - const Module = require('module') as { _resolveFilename: NodeResolveFilename } + const Module = _require('module') as { _resolveFilename: NodeResolveFilename } const prevResolver = Module._resolveFilename Module._resolveFilename = getResolver(prevResolver) return () => { diff --git a/packages/vite/src/node/plugins/terser.ts b/packages/vite/src/node/plugins/terser.ts index 9dc546191d14f7..4e6b7b681a2cea 100644 --- a/packages/vite/src/node/plugins/terser.ts +++ b/packages/vite/src/node/plugins/terser.ts @@ -1,8 +1,13 @@ +import { dirname } from 'path' +import { fileURLToPath } from 'url' import { Worker } from 'okie' import type { Terser } from 'types/terser' import type { Plugin } from '../plugin' import type { ResolvedConfig } from '..' +// TODO: use import() +const _dirname = dirname(fileURLToPath(import.meta.url)) + export function terserPlugin(config: ResolvedConfig): Plugin { const makeWorker = () => new Worker( @@ -10,10 +15,11 @@ export function terserPlugin(config: ResolvedConfig): Plugin { // when vite is linked, the worker thread won't share the same resolve // root with vite itself, so we have to pass in the basedir and resolve // terser first. - // eslint-disable-next-line node/no-restricted-require + // eslint-disable-next-line node/no-restricted-require, no-restricted-globals const terserPath = require.resolve('terser', { paths: [basedir] }) + // eslint-disable-next-line no-restricted-globals return require(terserPath).minify(code, options) as Terser.MinifyOutput } ) @@ -44,7 +50,7 @@ export function terserPlugin(config: ResolvedConfig): Plugin { // Lazy load worker. worker ||= makeWorker() - const res = await worker.run(__dirname, code, { + const res = await worker.run(_dirname, code, { safari10: true, ...config.build.terserOptions, sourceMap: !!outputOptions.sourcemap, diff --git a/packages/vite/src/node/publicUtils.ts b/packages/vite/src/node/publicUtils.ts new file mode 100644 index 00000000000000..43234eef2bcf0c --- /dev/null +++ b/packages/vite/src/node/publicUtils.ts @@ -0,0 +1,13 @@ +/** + * Exported sync utils should go here. + * This file will be bundled to ESM and CJS and redirected by ../index.cjs + * Please control the side-effects by checking the ./dist/node-cjs/publicUtils.cjs bundle + */ +export { + splitVendorChunkPlugin, + splitVendorChunk +} from './plugins/splitVendorChunk' +export { normalizePath, mergeConfig, mergeAlias } from './utils' +export { send } from './server/send' +export { createLogger } from './logger' +export { searchForWorkspaceRoot } from './server/searchRoot' diff --git a/packages/vite/src/node/server/__tests__/search-root.spec.ts b/packages/vite/src/node/server/__tests__/search-root.spec.ts index 066b64917a8b92..06bc8ab0947173 100644 --- a/packages/vite/src/node/server/__tests__/search-root.spec.ts +++ b/packages/vite/src/node/server/__tests__/search-root.spec.ts @@ -1,7 +1,10 @@ -import { resolve } from 'path' +import { dirname, resolve } from 'path' +import { fileURLToPath } from 'url' import { describe, expect, test } from 'vitest' import { searchForWorkspaceRoot } from '../searchRoot' +const __dirname = dirname(fileURLToPath(import.meta.url)) + describe('searchForWorkspaceRoot', () => { test('lerna', () => { const resolved = searchForWorkspaceRoot( diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index 4dafb7fc99b335..e59de9f2e50690 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -14,8 +14,13 @@ import type { SourceMap } from 'rollup' import type { CommonServerOptions } from '../http' import { httpServerStart, resolveHttpServer, resolveHttpsConfig } from '../http' import type { InlineConfig, ResolvedConfig } from '../config' -import { mergeConfig, resolveConfig } from '../config' -import { isParentDirectory, normalizePath, resolveHostname } from '../utils' +import { resolveConfig } from '../config' +import { + isParentDirectory, + mergeConfig, + normalizePath, + resolveHostname +} from '../utils' import { ssrLoadModule } from '../ssr/ssrModuleLoader' import { resolveSSRExternal } from '../ssr/ssrExternal' import { diff --git a/packages/vite/src/node/server/middlewares/static.ts b/packages/vite/src/node/server/middlewares/static.ts index f2508032ddb565..5d4a948885baa3 100644 --- a/packages/vite/src/node/server/middlewares/static.ts +++ b/packages/vite/src/node/server/middlewares/static.ts @@ -3,7 +3,7 @@ import type { ServerResponse } from 'http' import type { Options } from 'sirv' import sirv from 'sirv' import type { Connect } from 'types/connect' -import { isMatch } from 'micromatch' +import micromatch from 'micromatch' import type { ViteDevServer } from '../..' import { FS_PREFIX } from '../../constants' import { @@ -18,6 +18,8 @@ import { slash } from '../../utils' +const { isMatch } = micromatch + const sirvOptions: Options = { dev: true, etag: true, diff --git a/packages/vite/src/node/server/openBrowser.ts b/packages/vite/src/node/server/openBrowser.ts index bacb4006ea08ea..94349af970f1fb 100644 --- a/packages/vite/src/node/server/openBrowser.ts +++ b/packages/vite/src/node/server/openBrowser.ts @@ -8,12 +8,13 @@ * */ -import path from 'path' +import { join } from 'path' import { execSync } from 'child_process' import open from 'open' import spawn from 'cross-spawn' import colors from 'picocolors' import type { Logger } from '../logger' +import { VITE_PACKAGE_DIR } from '../constants' // https://github.com/sindresorhus/open#app const OSX_CHROME = 'google chrome' @@ -72,7 +73,7 @@ function startBrowserProcess(browser: string | undefined, url: string) { // on OS X Google Chrome with AppleScript execSync('ps cax | grep "Google Chrome"') execSync('osascript openChrome.applescript "' + encodeURI(url) + '"', { - cwd: path.dirname(require.resolve('vite/bin/openChrome.applescript')), + cwd: join(VITE_PACKAGE_DIR, 'bin'), stdio: 'ignore' }) return true diff --git a/packages/vite/src/node/server/pluginContainer.ts b/packages/vite/src/node/server/pluginContainer.ts index ce021112779d4b..4d124759af0afa 100644 --- a/packages/vite/src/node/server/pluginContainer.ts +++ b/packages/vite/src/node/server/pluginContainer.ts @@ -32,6 +32,7 @@ SOFTWARE. import fs from 'fs' import { join, resolve } from 'path' import { performance } from 'perf_hooks' +import { createRequire } from 'module' import type { EmittedFile, InputOptions, @@ -151,8 +152,14 @@ export async function createPluginContainer( const watchFiles = new Set() + // TODO: use import() + const _require = createRequire(import.meta.url) + // get rollup version - const rollupPkgPath = resolve(require.resolve('rollup'), '../../package.json') + const rollupPkgPath = resolve( + _require.resolve('rollup'), + '../../package.json' + ) const minimalContext: MinimalPluginContext = { meta: { rollupVersion: JSON.parse(fs.readFileSync(rollupPkgPath, 'utf-8')) diff --git a/packages/vite/src/node/ssr/__tests__/ssrModuleLoader.spec.ts b/packages/vite/src/node/ssr/__tests__/ssrModuleLoader.spec.ts index 4b494524d45c72..f69eedc409a007 100644 --- a/packages/vite/src/node/ssr/__tests__/ssrModuleLoader.spec.ts +++ b/packages/vite/src/node/ssr/__tests__/ssrModuleLoader.spec.ts @@ -1,15 +1,17 @@ import { resolve } from 'path' +import { fileURLToPath } from 'url' import { expect, test, vi } from 'vitest' import { createServer } from '../../index' -const badjs = resolve(__dirname, './fixtures/ssrModuleLoader-bad.js') +const __filename = fileURLToPath(import.meta.url) +const badjs = resolve(__filename, '../fixtures/ssrModuleLoader-bad.js') const THROW_MESSAGE = 'it is an expected error' test('always throw error when evaluating an wrong SSR module', async () => { const viteServer = await createServer() const spy = vi.spyOn(console, 'error').mockImplementation(() => {}) const expectedErrors = [] - for (const i of [0, 1]) { + for (const _ of [0, 1]) { try { await viteServer.ssrLoadModule(badjs) } catch (e) { diff --git a/packages/vite/src/node/ssr/ssrExternal.ts b/packages/vite/src/node/ssr/ssrExternal.ts index 24a12cb7386ff3..52b7a47566ba2a 100644 --- a/packages/vite/src/node/ssr/ssrExternal.ts +++ b/packages/vite/src/node/ssr/ssrExternal.ts @@ -1,5 +1,6 @@ import fs from 'fs' import path from 'path' +import { createRequire } from 'module' import { createFilter } from '@rollup/pluginutils' import type { InternalResolveOptions } from '../plugins/resolve' import { tryNodeResolve } from '../plugins/resolve' @@ -82,6 +83,9 @@ export function resolveSSRExternal( const CJS_CONTENT_RE = /\bmodule\.exports\b|\bexports[.\[]|\brequire\s*\(|\bObject\.(defineProperty|defineProperties|assign)\s*\(\s*exports\b/ +// TODO: use import() +const _require = createRequire(import.meta.url) + // do we need to do this ahead of time or could we do it lazily? function collectExternals( root: string, @@ -116,6 +120,7 @@ function collectExternals( let esmEntry: string | undefined let requireEntry: string + try { esmEntry = tryNodeResolve( id, @@ -127,7 +132,7 @@ function collectExternals( )?.id // normalizePath required for windows. tryNodeResolve uses normalizePath // which returns with '/', require.resolve returns with '\\' - requireEntry = normalizePath(require.resolve(id, { paths: [root] })) + requireEntry = normalizePath(_require.resolve(id, { paths: [root] })) } catch (e) { try { // no main entry, but deep imports may be allowed diff --git a/packages/vite/src/node/tsconfig.json b/packages/vite/src/node/tsconfig.json index 4116608b4d5a4f..7182bc6602ead7 100644 --- a/packages/vite/src/node/tsconfig.json +++ b/packages/vite/src/node/tsconfig.json @@ -5,7 +5,7 @@ "compilerOptions": { "target": "ES2020", "outDir": "../../dist/node", - "module": "CommonJS", + "module": "ESNext", "lib": ["ESNext", "DOM"], "sourceMap": true } diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts index d537eb9d971673..dc0d979d664450 100644 --- a/packages/vite/src/node/utils.ts +++ b/packages/vite/src/node/utils.ts @@ -4,7 +4,7 @@ import path from 'path' import { createHash } from 'crypto' import { promisify } from 'util' import { URL, URLSearchParams, pathToFileURL } from 'url' -import { builtinModules } from 'module' +import { builtinModules, createRequire } from 'module' import { performance } from 'perf_hooks' import resolve from 'resolve' import type { FSWatcher } from 'chokidar' @@ -12,6 +12,7 @@ import remapping from '@ampproject/remapping' import type { DecodedSourceMap, RawSourceMap } from '@ampproject/remapping' import colors from 'picocolors' import debug from 'debug' +import type { Alias, AliasOptions } from 'types/alias' import { CLIENT_ENTRY, CLIENT_PUBLIC_PATH, @@ -70,8 +71,12 @@ export const bareImportRE = /^[\w@](?!.*:\/\/)/ export const deepImportRE = /^([^@][^/]*)\/|^(@[^/]+\/[^/]+)\// export let isRunningWithYarnPnp: boolean + +// TODO: use import() +const _require = createRequire(import.meta.url) + try { - isRunningWithYarnPnp = Boolean(require('pnpapi')) + isRunningWithYarnPnp = Boolean(_require('pnpapi')) } catch {} const ssrExtensions = ['.js', '.cjs', '.json', '.node'] @@ -775,7 +780,7 @@ export const usingDynamicImport = typeof jest === 'undefined' */ export const dynamicImport = usingDynamicImport ? new Function('file', 'return import(file)') - : require + : _require export function parseRequest(id: string): Record | null { const [_, search] = id.split(requestQuerySplitRE, 2) @@ -832,3 +837,115 @@ function gracefulRename( export function emptyCssComments(raw: string) { return raw.replace(multilineCommentsRE, (s) => ' '.repeat(s.length)) } + +function mergeConfigRecursively( + defaults: Record, + overrides: Record, + rootPath: string +) { + const merged: Record = { ...defaults } + for (const key in overrides) { + const value = overrides[key] + if (value == null) { + continue + } + + const existing = merged[key] + + if (existing == null) { + merged[key] = value + continue + } + + // fields that require special handling + if (key === 'alias' && (rootPath === 'resolve' || rootPath === '')) { + merged[key] = mergeAlias(existing, value) + continue + } else if (key === 'assetsInclude' && rootPath === '') { + merged[key] = [].concat(existing, value) + continue + } else if ( + key === 'noExternal' && + rootPath === 'ssr' && + (existing === true || value === true) + ) { + merged[key] = true + continue + } + + if (Array.isArray(existing) || Array.isArray(value)) { + merged[key] = [...arraify(existing ?? []), ...arraify(value ?? [])] + continue + } + if (isObject(existing) && isObject(value)) { + merged[key] = mergeConfigRecursively( + existing, + value, + rootPath ? `${rootPath}.${key}` : key + ) + continue + } + + merged[key] = value + } + return merged +} + +export function mergeConfig( + defaults: Record, + overrides: Record, + isRoot = true +): Record { + return mergeConfigRecursively(defaults, overrides, isRoot ? '' : '.') +} + +export function mergeAlias( + a?: AliasOptions, + b?: AliasOptions +): AliasOptions | undefined { + if (!a) return b + if (!b) return a + if (isObject(a) && isObject(b)) { + return { ...a, ...b } + } + // the order is flipped because the alias is resolved from top-down, + // where the later should have higher priority + return [...normalizeAlias(b), ...normalizeAlias(a)] +} + +export function normalizeAlias(o: AliasOptions = []): Alias[] { + return Array.isArray(o) + ? o.map(normalizeSingleAlias) + : Object.keys(o).map((find) => + normalizeSingleAlias({ + find, + replacement: (o as any)[find] + }) + ) +} + +// https://github.com/vitejs/vite/issues/1363 +// work around https://github.com/rollup/plugins/issues/759 +function normalizeSingleAlias({ + find, + replacement, + customResolver +}: Alias): Alias { + if ( + typeof find === 'string' && + find.endsWith('/') && + replacement.endsWith('/') + ) { + find = find.slice(0, find.length - 1) + replacement = replacement.slice(0, replacement.length - 1) + } + + const alias: Alias = { + find, + replacement + } + if (customResolver) { + alias.customResolver = customResolver + } + return alias +} diff --git a/packages/vite/tsconfig.base.json b/packages/vite/tsconfig.base.json index 1b417945798eaa..37eb865185662d 100644 --- a/packages/vite/tsconfig.base.json +++ b/packages/vite/tsconfig.base.json @@ -1,6 +1,7 @@ { "compilerOptions": { "target": "ES2020", + "module": "ESNext", "moduleResolution": "node", "strict": true, "declaration": true, diff --git a/playground/vitestSetup.ts b/playground/vitestSetup.ts index 660abc9307b848..70ac682d7bec27 100644 --- a/playground/vitestSetup.ts +++ b/playground/vitestSetup.ts @@ -161,7 +161,7 @@ export async function startDefaultServe() { let config: InlineConfig | undefined if (fs.existsSync(testCustomConfig)) { // test has custom server configuration. - config = require(testCustomConfig) + config = await import(testCustomConfig).then((r) => r.default) } const options: InlineConfig = { diff --git a/playground/vue-sourcemap/__tests__/serve.spec.ts b/playground/vue-sourcemap/__tests__/serve.spec.ts index 278b04bef1e6f5..83e63c56f85a7a 100644 --- a/playground/vue-sourcemap/__tests__/serve.spec.ts +++ b/playground/vue-sourcemap/__tests__/serve.spec.ts @@ -15,7 +15,7 @@ describe.runIf(isServe)('serve:vue-sourcemap', () => { return text } } - throw new Error('Not found') + throw new Error('Style not found: ' + content) } test('js', async () => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2831a1dfd11d23..b4f5275c59186b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -54,6 +54,7 @@ importers: prompts: ^2.4.2 rimraf: ^3.0.2 rollup: ^2.72.1 + rollup-plugin-esbuild: ^4.9.1 semver: ^7.3.7 simple-git-hooks: ^2.7.0 sirv: ^2.0.2 @@ -109,6 +110,7 @@ importers: prompts: 2.4.2 rimraf: 3.0.2 rollup: 2.72.1 + rollup-plugin-esbuild: 4.9.1_pnyrwy4zazm4ltxxkdqkpc4vuy semver: 7.3.7 simple-git-hooks: 2.7.0 sirv: 2.0.2 @@ -239,6 +241,7 @@ importers: dotenv-expand: ^5.1.0 es-module-lexer: ^0.10.5 esbuild: ^0.14.38 + esno: ^0.16.3 estree-walker: ^2.0.2 etag: ^1.8.1 fast-glob: ^3.2.11 @@ -305,6 +308,7 @@ importers: dotenv: 14.3.2 dotenv-expand: 5.1.0 es-module-lexer: 0.10.5 + esno: 0.16.3 estree-walker: 2.0.2 etag: 1.8.1 fast-glob: 3.2.11 @@ -1608,6 +1612,27 @@ packages: resolution: {integrity: sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==} dev: false + /@esbuild-kit/cjs-loader/2.0.0: + resolution: {integrity: sha512-1ijCpmiCQcOcr0dmwwwCpzv0inWpNtEgiXDWc74AL52AhvY108M26suhWe9PMDcF1esnPJf0YSeVBLLZS6SQvg==} + dependencies: + '@esbuild-kit/core-utils': 1.1.1 + get-tsconfig: 3.0.1 + dev: true + + /@esbuild-kit/core-utils/1.1.1: + resolution: {integrity: sha512-Y5QM1ip6nUvycH8dc/bfNPLNyVt2ccBLB09lAndUvfCza/UwkAfYSiNMbcAnkLcEciEENMm6Lsyt1L98K57v3w==} + dependencies: + esbuild: 0.14.38 + dev: true + + /@esbuild-kit/esm-loader/2.1.0: + resolution: {integrity: sha512-zE7BepoWvVhyoHDEnc2whD9x27UzdRIcUguOD6mUx8PzYjEw2pdNsY+2sP9PeGxW6YJvtLOqcDj3tqhxj61IPw==} + dependencies: + '@esbuild-kit/core-utils': 1.1.1 + es-module-lexer: 0.10.5 + get-tsconfig: 3.0.1 + dev: true + /@eslint/eslintrc/1.2.3: resolution: {integrity: sha512-uGo44hIwoLGNyduRpjdEpovcbMdd+Nv7amtmJxnKmI8xj6yd5LncmSwDa5NgX/41lIFJtkjD6YdVfgEzPfJ5UA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -4352,6 +4377,13 @@ packages: - supports-color dev: true + /esno/0.16.3: + resolution: {integrity: sha512-6slSBEV1lMKcX13DBifvnDFpNno5WXhw4j/ff7RI0y51BZiDqEe5dNhhjhIQ3iCOQuzsm2MbVzmwqbN78BBhPg==} + hasBin: true + dependencies: + tsx: 3.4.0 + dev: true + /espree/9.3.2: resolution: {integrity: sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -4753,6 +4785,10 @@ packages: resolution: {integrity: sha1-dKILqKSr7OWuGZrQPyvMaP38m6U=} dev: true + /get-tsconfig/3.0.1: + resolution: {integrity: sha512-+m30eQjbcf3xMNdnacXH5IDAKUMbI7Mhbf3e1BHif1FzBlUhBzBlmOVc7kL4+kB035l8OCyBdI3dNXZ3of9HqA==} + dev: true + /git-raw-commits/2.0.11: resolution: {integrity: sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A==} engines: {node: '>=10'} @@ -8128,6 +8164,17 @@ packages: typescript: 4.6.4 dev: true + /tsx/3.4.0: + resolution: {integrity: sha512-WWakMoC5OqUXvOVZuyAySyETjAZ9rJxZXRbbOhYXDCeHF95hQUBa07UwUFu1yprlnrJ/W7XWfA99YTNKO//KxQ==} + hasBin: true + dependencies: + '@esbuild-kit/cjs-loader': 2.0.0 + '@esbuild-kit/core-utils': 1.1.1 + '@esbuild-kit/esm-loader': 2.1.0 + optionalDependencies: + fsevents: 2.3.2 + dev: true + /type-check/0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'}