Permalink
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
core/rollup.config.js /
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
380 lines (344 sloc)
11.7 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// @ts-check | |
import { createRequire } from 'node:module' | |
import { fileURLToPath } from 'node:url' | |
import path from 'node:path' | |
import replace from '@rollup/plugin-replace' | |
import json from '@rollup/plugin-json' | |
import chalk from 'chalk' | |
import commonJS from '@rollup/plugin-commonjs' | |
import polyfillNode from 'rollup-plugin-polyfill-node' | |
import { nodeResolve } from '@rollup/plugin-node-resolve' | |
import terser from '@rollup/plugin-terser' | |
import esbuild from 'rollup-plugin-esbuild' | |
import alias from '@rollup/plugin-alias' | |
import { entries } from './scripts/aliases.js' | |
import { constEnum } from './scripts/const-enum.js' | |
if (!process.env.TARGET) { | |
throw new Error('TARGET package must be specified via --environment flag.') | |
} | |
const require = createRequire(import.meta.url) | |
const __dirname = fileURLToPath(new URL('.', import.meta.url)) | |
const masterVersion = require('./package.json').version | |
const consolidatePkg = require('@vue/consolidate/package.json') | |
const packagesDir = path.resolve(__dirname, 'packages') | |
const packageDir = path.resolve(packagesDir, process.env.TARGET) | |
const resolve = p => path.resolve(packageDir, p) | |
const pkg = require(resolve(`package.json`)) | |
const packageOptions = pkg.buildOptions || {} | |
const name = packageOptions.filename || path.basename(packageDir) | |
const [enumPlugin, enumDefines] = constEnum() | |
const outputConfigs = { | |
'esm-bundler': { | |
file: resolve(`dist/${name}.esm-bundler.js`), | |
format: `es` | |
}, | |
'esm-browser': { | |
file: resolve(`dist/${name}.esm-browser.js`), | |
format: `es` | |
}, | |
cjs: { | |
file: resolve(`dist/${name}.cjs.js`), | |
format: `cjs` | |
}, | |
global: { | |
file: resolve(`dist/${name}.global.js`), | |
format: `iife` | |
}, | |
// runtime-only builds, for main "vue" package only | |
'esm-bundler-runtime': { | |
file: resolve(`dist/${name}.runtime.esm-bundler.js`), | |
format: `es` | |
}, | |
'esm-browser-runtime': { | |
file: resolve(`dist/${name}.runtime.esm-browser.js`), | |
format: 'es' | |
}, | |
'global-runtime': { | |
file: resolve(`dist/${name}.runtime.global.js`), | |
format: 'iife' | |
} | |
} | |
const defaultFormats = ['esm-bundler', 'cjs'] | |
const inlineFormats = process.env.FORMATS && process.env.FORMATS.split(',') | |
const packageFormats = inlineFormats || packageOptions.formats || defaultFormats | |
const packageConfigs = process.env.PROD_ONLY | |
? [] | |
: packageFormats.map(format => createConfig(format, outputConfigs[format])) | |
if (process.env.NODE_ENV === 'production') { | |
packageFormats.forEach(format => { | |
if (packageOptions.prod === false) { | |
return | |
} | |
if (format === 'cjs') { | |
packageConfigs.push(createProductionConfig(format)) | |
} | |
if (/^(global|esm-browser)(-runtime)?/.test(format)) { | |
packageConfigs.push(createMinifiedConfig(format)) | |
} | |
}) | |
} | |
export default packageConfigs | |
function createConfig(format, output, plugins = []) { | |
if (!output) { | |
console.log(chalk.yellow(`invalid format: "${format}"`)) | |
process.exit(1) | |
} | |
const isProductionBuild = | |
process.env.__DEV__ === 'false' || /\.prod\.js$/.test(output.file) | |
const isBundlerESMBuild = /esm-bundler/.test(format) | |
const isBrowserESMBuild = /esm-browser/.test(format) | |
const isServerRenderer = name === 'server-renderer' | |
const isNodeBuild = format === 'cjs' | |
const isGlobalBuild = /global/.test(format) | |
const isCompatPackage = pkg.name === '@vue/compat' | |
const isCompatBuild = !!packageOptions.compat | |
const isBrowserBuild = | |
(isGlobalBuild || isBrowserESMBuild || isBundlerESMBuild) && | |
!packageOptions.enableNonBrowserBranches | |
output.exports = isCompatPackage ? 'auto' : 'named' | |
if (isNodeBuild) { | |
output.esModule = true | |
} | |
output.sourcemap = !!process.env.SOURCE_MAP | |
output.externalLiveBindings = false | |
if (isGlobalBuild) { | |
output.name = packageOptions.name | |
} | |
let entryFile = /runtime$/.test(format) ? `src/runtime.ts` : `src/index.ts` | |
// the compat build needs both default AND named exports. This will cause | |
// Rollup to complain for non-ESM targets, so we use separate entries for | |
// esm vs. non-esm builds. | |
if (isCompatPackage && (isBrowserESMBuild || isBundlerESMBuild)) { | |
entryFile = /runtime$/.test(format) | |
? `src/esm-runtime.ts` | |
: `src/esm-index.ts` | |
} | |
function resolveDefine() { | |
const replacements = { | |
__COMMIT__: `"${process.env.COMMIT}"`, | |
__VERSION__: `"${masterVersion}"`, | |
// this is only used during Vue's internal tests | |
__TEST__: `false`, | |
// If the build is expected to run directly in the browser (global / esm builds) | |
__BROWSER__: String(isBrowserBuild), | |
__GLOBAL__: String(isGlobalBuild), | |
__ESM_BUNDLER__: String(isBundlerESMBuild), | |
__ESM_BROWSER__: String(isBrowserESMBuild), | |
// is targeting Node (SSR)? | |
__NODE_JS__: String(isNodeBuild), | |
// need SSR-specific branches? | |
__SSR__: String(isNodeBuild || isBundlerESMBuild || isServerRenderer), | |
// 2.x compat build | |
__COMPAT__: String(isCompatBuild), | |
// feature flags | |
__FEATURE_SUSPENSE__: `true`, | |
__FEATURE_OPTIONS_API__: isBundlerESMBuild | |
? `__VUE_OPTIONS_API__` | |
: `true`, | |
__FEATURE_PROD_DEVTOOLS__: isBundlerESMBuild | |
? `__VUE_PROD_DEVTOOLS__` | |
: `false` | |
} | |
if (!isBundlerESMBuild) { | |
// hard coded dev/prod builds | |
// @ts-ignore | |
replacements.__DEV__ = String(!isProductionBuild) | |
} | |
// allow inline overrides like | |
//__RUNTIME_COMPILE__=true pnpm build runtime-core | |
Object.keys(replacements).forEach(key => { | |
if (key in process.env) { | |
replacements[key] = process.env[key] | |
} | |
}) | |
return replacements | |
} | |
// esbuild define is a bit strict and only allows literal json or identifiers | |
// so we still need replace plugin in some cases | |
function resolveReplace() { | |
const replacements = { ...enumDefines } | |
if (isProductionBuild && isBrowserBuild) { | |
Object.assign(replacements, { | |
'context.onError(': `/*#__PURE__*/ context.onError(`, | |
'emitError(': `/*#__PURE__*/ emitError(`, | |
'createCompilerError(': `/*#__PURE__*/ createCompilerError(`, | |
'createDOMCompilerError(': `/*#__PURE__*/ createDOMCompilerError(` | |
}) | |
} | |
if (isBundlerESMBuild) { | |
Object.assign(replacements, { | |
// preserve to be handled by bundlers | |
__DEV__: `process.env.NODE_ENV !== 'production'` | |
}) | |
} | |
// for compiler-sfc browser build inlined deps | |
if (isBrowserESMBuild) { | |
Object.assign(replacements, { | |
'process.env': '({})', | |
'process.platform': '""', | |
'process.stdout': 'null' | |
}) | |
} | |
if (Object.keys(replacements).length) { | |
// @ts-ignore | |
return [replace({ values: replacements, preventAssignment: true })] | |
} else { | |
return [] | |
} | |
} | |
function resolveExternal() { | |
const treeShakenDeps = ['source-map', '@babel/parser', 'estree-walker'] | |
if (isGlobalBuild || isBrowserESMBuild || isCompatPackage) { | |
if (!packageOptions.enableNonBrowserBranches) { | |
// normal browser builds - non-browser only imports are tree-shaken, | |
// they are only listed here to suppress warnings. | |
return treeShakenDeps | |
} | |
} else { | |
// Node / esm-bundler builds. | |
// externalize all direct deps unless it's the compat build. | |
return [ | |
...Object.keys(pkg.dependencies || {}), | |
...Object.keys(pkg.peerDependencies || {}), | |
// for @vue/compiler-sfc / server-renderer | |
...['path', 'url', 'stream'], | |
// somehow these throw warnings for runtime-* package builds | |
...treeShakenDeps | |
] | |
} | |
} | |
function resolveNodePlugins() { | |
// we are bundling forked consolidate.js in compiler-sfc which dynamically | |
// requires a ton of template engines which should be ignored. | |
let cjsIgnores = [] | |
if (pkg.name === '@vue/compiler-sfc') { | |
cjsIgnores = [ | |
...Object.keys(consolidatePkg.devDependencies), | |
'vm', | |
'crypto', | |
'react-dom/server', | |
'teacup/lib/express', | |
'arc-templates/dist/es5', | |
'then-pug', | |
'then-jade' | |
] | |
} | |
const nodePlugins = | |
(format === 'cjs' && Object.keys(pkg.devDependencies || {}).length) || | |
packageOptions.enableNonBrowserBranches | |
? [ | |
commonJS({ | |
sourceMap: false, | |
ignore: cjsIgnores | |
}), | |
...(format === 'cjs' ? [] : [polyfillNode()]), | |
nodeResolve() | |
] | |
: [] | |
if (format === 'cjs') { | |
nodePlugins.push(cjsReExportsPatchPlugin()) | |
} | |
return nodePlugins | |
} | |
return { | |
input: resolve(entryFile), | |
// Global and Browser ESM builds inlines everything so that they can be | |
// used alone. | |
external: resolveExternal(), | |
plugins: [ | |
json({ | |
namedExports: false | |
}), | |
alias({ | |
entries | |
}), | |
enumPlugin, | |
...resolveReplace(), | |
esbuild({ | |
tsconfig: path.resolve(__dirname, 'tsconfig.json'), | |
sourceMap: output.sourcemap, | |
minify: false, | |
target: isServerRenderer || isNodeBuild ? 'es2019' : 'es2015', | |
define: resolveDefine() | |
}), | |
...resolveNodePlugins(), | |
...plugins | |
], | |
output, | |
onwarn: (msg, warn) => { | |
if (!/Circular/.test(msg)) { | |
warn(msg) | |
} | |
}, | |
treeshake: { | |
moduleSideEffects: false | |
} | |
} | |
} | |
function createProductionConfig(format) { | |
return createConfig(format, { | |
file: resolve(`dist/${name}.${format}.prod.js`), | |
format: outputConfigs[format].format | |
}) | |
} | |
function createMinifiedConfig(format) { | |
return createConfig( | |
format, | |
{ | |
file: outputConfigs[format].file.replace(/\.js$/, '.prod.js'), | |
format: outputConfigs[format].format | |
}, | |
[ | |
terser({ | |
module: /^esm/.test(format), | |
compress: { | |
ecma: 2015, | |
pure_getters: true | |
}, | |
safari10: true | |
}) | |
] | |
) | |
} | |
// temporary patch for https://github.com/nodejs/cjs-module-lexer/issues/79 | |
// | |
// When importing a cjs module from esm, Node.js uses cjs-module-lexer to | |
// detect * re-exports from other packages. However, the detection logic is | |
// fragile and breaks when Rollup generates different code for the re-exports. | |
// We were locked on an old version of Rollup because of this. | |
// | |
// The latest versions of Node ships an updated version of cjs-module-lexer that | |
// has fixed https://github.com/nodejs/cjs-module-lexer/issues/38, however we | |
// still need to support older versions of Node that does not have the latest | |
// version of cjs-module-lexer (Node < 14.18) | |
// | |
// At the same time, we want to upgrade to Rollup 3 so we are not forever locked | |
// on an old version of Rollup. | |
// | |
// What this patch does: | |
// 1. Rewrite the for...in loop to Object.keys() so cjs-module-lexer can find it | |
// The for...in loop is only used when output.externalLiveBindings is set to | |
// false, and we do want to set it to false to avoid perf costs during SSR. | |
// 2. Also remove exports.hasOwnProperty check, which breaks the detection in | |
// Node.js versions that | |
// | |
// TODO in the future, we should no longer rely on this if we inline all deps | |
// in the main `vue` package. | |
function cjsReExportsPatchPlugin() { | |
const matcher = | |
/for \(var k in (\w+)\) {(\s+if \(k !== 'default') && !exports.hasOwnProperty\(k\)(\) exports\[k\] = (?:\w+)\[k\];\s+)}/ | |
return { | |
name: 'patch-cjs-re-exports', | |
renderChunk(code, _, options) { | |
if (matcher.test(code)) { | |
return code.replace(matcher, (_, r1, r2, r3) => { | |
return `Object.keys(${r1}).forEach(function(k) {${r2}${r3}});` | |
}) | |
} else if (options.file.endsWith('packages/vue/dist/vue.cjs.js')) { | |
// make sure we don't accidentally miss the rewrite in case Rollup | |
// changes the output again. | |
throw new Error('cjs build re-exports rewrite failed.') | |
} | |
} | |
} | |
} |