2021-03-01 15:06:11 +08:00

530 lines
23 KiB
JavaScript

"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ssrBuild = exports.build = exports.createBaseRollupPlugins = exports.onRollupWarning = void 0;
const path_1 = __importDefault(require("path"));
const fs_extra_1 = __importDefault(require("fs-extra"));
const chalk_1 = __importDefault(require("chalk"));
const p_map_series_1 = __importDefault(require("p-map-series"));
const json_1 = require("klona/json");
const utils_1 = require("../utils");
const resolver_1 = require("../resolver");
const buildPluginResolve_1 = require("./buildPluginResolve");
const buildPluginHtml_1 = require("./buildPluginHtml");
const buildPluginCss_1 = require("./buildPluginCss");
const buildPluginAsset_1 = require("./buildPluginAsset");
const buildPluginEsbuild_1 = require("./buildPluginEsbuild");
const buildPluginReplace_1 = require("./buildPluginReplace");
const esbuildService_1 = require("../esbuildService");
const config_1 = require("../config");
const transform_1 = require("../transform");
const hash_sum_1 = __importDefault(require("hash-sum"));
const cssUtils_1 = require("../utils/cssUtils");
const buildPluginWasm_1 = require("./buildPluginWasm");
const buildPluginManifest_1 = require("./buildPluginManifest");
const writeColors = {
[0 /* JS */]: chalk_1.default.cyan,
[1 /* CSS */]: chalk_1.default.magenta,
[2 /* ASSET */]: chalk_1.default.green,
[3 /* HTML */]: chalk_1.default.blue,
[4 /* SOURCE_MAP */]: chalk_1.default.gray
};
const warningIgnoreList = [`CIRCULAR_DEPENDENCY`, `THIS_IS_UNDEFINED`];
const dynamicImportWarningIgnoreList = [
`Unsupported expression`,
`statically analyzed`
];
const isBuiltin = require('isbuiltin');
function onRollupWarning(spinner, options) {
return (warning, warn) => {
if (warning.code === 'UNRESOLVED_IMPORT') {
let message;
const id = warning.source;
const importer = warning.importer;
if (isBuiltin(id)) {
let importingDep;
if (importer) {
const pkg = JSON.parse(utils_1.lookupFile(importer, ['package.json']) || `{}`);
if (pkg.name) {
importingDep = pkg.name;
}
}
const allowList = options.allowNodeBuiltins;
if (importingDep && allowList && allowList.includes(importingDep)) {
return;
}
const dep = importingDep
? `Dependency ${chalk_1.default.yellow(importingDep)}`
: `A dependency`;
message =
`${dep} is attempting to import Node built-in module ${chalk_1.default.yellow(id)}.\n` +
`This will not work in a browser environment.\n` +
`Imported by: ${chalk_1.default.gray(importer)}`;
}
else {
message =
`[vite]: Rollup failed to resolve import "${warning.source}" from "${warning.importer}".\n` +
`This is most likely unintended because it can break your application at runtime.\n` +
`If you do want to externalize this module explicitly add it to\n` +
`\`rollupInputOptions.external\``;
}
if (spinner) {
spinner.stop();
}
throw new Error(message);
}
if (warning.plugin === 'rollup-plugin-dynamic-import-variables' &&
dynamicImportWarningIgnoreList.some((msg) => warning.message.includes(msg))) {
return;
}
if (!warningIgnoreList.includes(warning.code)) {
// ora would swallow the console.warn if we let it keep running
// https://github.com/sindresorhus/ora/issues/90
if (spinner) {
spinner.stop();
}
warn(warning);
if (spinner) {
spinner.start();
}
}
};
}
exports.onRollupWarning = onRollupWarning;
/**
* Creates non-application specific plugins that are shared between the main
* app and the dependencies. This is used by the `optimize` command to
* pre-bundle dependencies.
*/
async function createBaseRollupPlugins(root, resolver, options) {
const { transforms = [], vueCustomBlockTransforms = {}, enableEsbuild = true, enableRollupPluginVue = true } = options;
const { nodeResolve } = require('@rollup/plugin-node-resolve');
const dynamicImport = require('rollup-plugin-dynamic-import-variables');
return [
// vite:resolve
buildPluginResolve_1.createBuildResolvePlugin(root, resolver),
// vite:esbuild
enableEsbuild ? await buildPluginEsbuild_1.createEsbuildPlugin(options.jsx) : null,
// vue
enableRollupPluginVue ? await createVuePlugin(root, options) : null,
require('@rollup/plugin-json')({
preferConst: true,
indent: ' ',
compact: false,
namedExports: true
}),
// user transforms
...(transforms.length || Object.keys(vueCustomBlockTransforms).length
? [transform_1.createBuildJsTransformPlugin(transforms, vueCustomBlockTransforms)]
: []),
nodeResolve({
rootDir: root,
extensions: resolver_1.supportedExts,
preferBuiltins: false,
dedupe: options.rollupDedupe || [],
mainFields: resolver_1.mainFields
}),
require('@rollup/plugin-commonjs')({
extensions: ['.js', '.cjs']
}),
dynamicImport({
warnOnError: true,
include: [/\.js$/],
exclude: [/node_modules/]
})
].filter(Boolean);
}
exports.createBaseRollupPlugins = createBaseRollupPlugins;
async function createVuePlugin(root, { vueCustomBlockTransforms = {}, rollupPluginVueOptions, cssPreprocessOptions, cssModuleOptions, vueCompilerOptions, vueTransformAssetUrls = {}, vueTemplatePreprocessOptions = {} }) {
const { options: postcssOptions, plugins: postcssPlugins } = await cssUtils_1.resolvePostcssOptions(root, true);
if (typeof vueTransformAssetUrls === 'object') {
vueTransformAssetUrls = {
includeAbsolute: true,
...vueTransformAssetUrls
};
}
return require('rollup-plugin-vue')({
...rollupPluginVueOptions,
templatePreprocessOptions: {
...vueTemplatePreprocessOptions,
pug: {
doctype: 'html',
...(vueTemplatePreprocessOptions && vueTemplatePreprocessOptions.pug)
}
},
transformAssetUrls: vueTransformAssetUrls,
postcssOptions,
postcssPlugins,
preprocessStyles: true,
preprocessOptions: cssPreprocessOptions,
preprocessCustomRequire: (id) => require(utils_1.resolveFrom(root, id)),
compilerOptions: vueCompilerOptions,
cssModulesOptions: {
localsConvention: 'camelCase',
generateScopedName: (local, filename) => `${local}_${hash_sum_1.default(filename)}`,
...cssModuleOptions,
...(rollupPluginVueOptions && rollupPluginVueOptions.cssModulesOptions)
},
customBlocks: Object.keys(vueCustomBlockTransforms)
});
}
/**
* Clone the given config object and fill it with default values.
*/
function prepareConfig(config) {
const { alias = {}, assetsDir = '_assets', assetsInclude = utils_1.isStaticAsset, assetsInlineLimit = 4096, base = '/', cssCodeSplit = true, cssModuleOptions = {}, cssPreprocessOptions = {}, define = {}, emitAssets = true, emitIndex = true, enableEsbuild = true, enableRollupPluginVue = true, entry = 'index.html', env = {}, esbuildTarget = 'es2020', indexHtmlTransforms = [], jsx = 'vue', minify = true, mode = 'production', optimizeDeps = {}, outDir = 'dist', resolvers = [], rollupDedupe = [], rollupInputOptions = {}, rollupOutputOptions = {}, rollupPluginVueOptions = {}, root = process.cwd(), shouldPreload = null, silent = false, sourcemap = false, terserOptions = {}, transforms = [], vueCompilerOptions = {}, vueCustomBlockTransforms = {}, vueTransformAssetUrls = {}, vueTemplatePreprocessOptions = {}, write = true } = json_1.klona(config);
return {
...config,
alias,
assetsDir,
assetsInclude,
assetsInlineLimit,
base,
cssCodeSplit,
cssModuleOptions,
cssPreprocessOptions,
define,
emitAssets,
emitIndex,
enableEsbuild,
enableRollupPluginVue,
entry,
env,
esbuildTarget,
indexHtmlTransforms,
jsx,
minify,
mode,
optimizeDeps,
outDir,
resolvers,
rollupDedupe,
rollupInputOptions,
rollupOutputOptions,
rollupPluginVueOptions,
root,
shouldPreload,
silent,
sourcemap,
terserOptions,
transforms,
vueCompilerOptions,
vueCustomBlockTransforms,
vueTransformAssetUrls,
vueTemplatePreprocessOptions,
write
};
}
/**
* Bundles the app for production.
* Returns a Promise containing the build result.
*/
async function build(options) {
const builds = [];
const config = prepareConfig(options);
const postBuildHooks = utils_1.toArray(config.configureBuild)
.map((configureBuild) => configureBuild(config, builds))
.filter(Boolean);
const { root, assetsDir, assetsInlineLimit, emitAssets, minify, silent, sourcemap, shouldPreload, env, mode: configMode, define: userDefineReplacements, write } = config;
const isTest = process.env.NODE_ENV === 'test';
const resolvedMode = process.env.VITE_ENV || configMode;
const start = Date.now();
let spinner;
const msg = `Building ${configMode} bundle...`;
if (!silent) {
if (process.env.DEBUG || isTest) {
console.log(msg);
}
else {
spinner = require('ora')(msg + '\n').start();
}
}
const outDir = path_1.default.resolve(root, config.outDir);
const indexPath = path_1.default.resolve(root, 'index.html');
const publicDir = path_1.default.join(root, 'public');
const publicBasePath = config.base.replace(/([^/])$/, '$1/'); // ensure ending slash
const resolvedAssetsPath = path_1.default.join(outDir, assetsDir);
const resolver = resolver_1.createResolver(root, config.resolvers, config.alias, config.assetsInclude);
const { htmlPlugin, renderIndex } = await buildPluginHtml_1.createBuildHtmlPlugin(root, indexPath, publicBasePath, assetsDir, assetsInlineLimit, resolver, shouldPreload, options);
const basePlugins = await createBaseRollupPlugins(root, resolver, config);
// https://github.com/darionco/rollup-plugin-web-worker-loader
// configured to support `import Worker from './my-worker?worker'`
// this plugin relies on resolveId and must be placed before node-resolve
// since the latter somehow swallows ids with query strings since 8.x
basePlugins.splice(basePlugins.findIndex((p) => p.name.includes('node-resolve')), 0, require('rollup-plugin-web-worker-loader')({
targetPlatform: 'browser',
pattern: /(.+)\?worker$/,
extensions: resolver_1.supportedExts,
sourcemap: false // it's inlined so it bloats the bundle
}));
// user env variables loaded from .env files.
// only those prefixed with VITE_ are exposed.
const userClientEnv = {};
const userEnvReplacements = {};
Object.keys(env).forEach((key) => {
if (key.startsWith(`VITE_`)) {
userEnvReplacements[`import.meta.env.${key}`] = JSON.stringify(env[key]);
userClientEnv[key] = env[key];
}
});
const builtInClientEnv = {
BASE_URL: publicBasePath,
MODE: configMode,
DEV: resolvedMode !== 'production',
PROD: resolvedMode === 'production'
};
const builtInEnvReplacements = {};
Object.keys(builtInClientEnv).forEach((key) => {
builtInEnvReplacements[`import.meta.env.${key}`] = JSON.stringify(builtInClientEnv[key]);
});
Object.keys(userDefineReplacements).forEach((key) => {
userDefineReplacements[key] = JSON.stringify(userDefineReplacements[key]);
});
const { pluginsPreBuild = [], plugins = [], pluginsPostBuild = [], pluginsOptimizer, ...rollupInputOptions } = config.rollupInputOptions;
builds.unshift({
input: config.entry,
preserveEntrySignatures: false,
treeshake: { moduleSideEffects: 'no-external' },
...rollupInputOptions,
output: config.rollupOutputOptions,
plugins: [
...plugins,
...pluginsPreBuild,
...basePlugins,
// vite:html
htmlPlugin,
// we use a custom replacement plugin because @rollup/plugin-replace
// performs replacements twice, once at transform and once at renderChunk
// - which makes it impossible to exclude Vue templates from it since
// Vue templates are compiled into js and included in chunks.
buildPluginReplace_1.createReplacePlugin((id) => !/\?vue&type=template/.test(id) &&
// also exclude css and static assets for performance
!cssUtils_1.isCSSRequest(id) &&
!resolver.isAssetRequest(id), {
...config_1.defaultDefines,
...userDefineReplacements,
...userEnvReplacements,
...builtInEnvReplacements,
'import.meta.env.': `({}).`,
'import.meta.env': JSON.stringify({
...userClientEnv,
...builtInClientEnv
}),
'process.env.NODE_ENV': JSON.stringify(resolvedMode),
'process.env.': `({}).`,
'process.env': JSON.stringify({ NODE_ENV: resolvedMode }),
'import.meta.hot': `false`
}, !!sourcemap),
// vite:css
buildPluginCss_1.createBuildCssPlugin({
root,
publicBase: publicBasePath,
assetsDir,
minify,
inlineLimit: assetsInlineLimit,
cssCodeSplit: config.cssCodeSplit,
preprocessOptions: config.cssPreprocessOptions,
modulesOptions: config.cssModuleOptions
}),
// vite:wasm
buildPluginWasm_1.createBuildWasmPlugin(root, publicBasePath, assetsDir, assetsInlineLimit),
// vite:asset
buildPluginAsset_1.createBuildAssetPlugin(root, resolver, publicBasePath, assetsDir, assetsInlineLimit),
config.enableEsbuild &&
buildPluginEsbuild_1.createEsbuildRenderChunkPlugin(config.esbuildTarget, minify === 'esbuild'),
// minify with terser
// this is the default which has better compression, but slow
// the user can opt-in to use esbuild which is much faster but results
// in ~8-10% larger file size.
minify && minify !== 'esbuild'
? require('rollup-plugin-terser').terser(config.terserOptions)
: undefined,
// #728 user plugins should apply after `@rollup/plugin-commonjs`
// #471#issuecomment-683318951 user plugin after internal plugin
...pluginsPostBuild,
// vite:manifest
config.emitManifest ? buildPluginManifest_1.createBuildManifestPlugin() : undefined
].filter(Boolean)
});
// lazy require rollup so that we don't load it when only using the dev server
// importing it just for the types
const rollup = require('rollup').rollup;
// multiple builds are processed sequentially, in case a build
// depends on the output of a preceding build.
const results = await p_map_series_1.default(builds, async (build, i) => {
const { output: outputOptions, onResult, ...inputOptions } = build;
let result;
let indexHtml;
let indexHtmlPath = getIndexHtmlOutputPath(build);
const emitIndex = config.emitIndex && indexHtmlPath !== null;
try {
const bundle = await rollup({
onwarn: onRollupWarning(spinner, config.optimizeDeps),
...inputOptions,
plugins: [
...(inputOptions.plugins || []).filter(
// remove vite:emit in case this build copied another build's plugins
(plugin) => plugin.name !== 'vite:emit'),
// vite:emit
createEmitPlugin(emitAssets, async (assets) => {
indexHtml = emitIndex ? await renderIndex(assets) : '';
result = { build, assets, html: indexHtml };
if (onResult) {
await onResult(result);
}
// run post-build hooks sequentially
await postBuildHooks.reduce((queue, hook) => queue.then(() => hook(result)), Promise.resolve());
if (write) {
if (i === 0) {
await fs_extra_1.default.emptyDir(outDir);
}
if (emitIndex) {
indexHtmlPath = path_1.default.join(outDir, indexHtmlPath);
await fs_extra_1.default.writeFile(indexHtmlPath, indexHtml);
}
}
})
]
});
await bundle[write ? 'write' : 'generate']({
dir: resolvedAssetsPath,
format: 'es',
sourcemap,
entryFileNames: `[name].[hash].js`,
chunkFileNames: `[name].[hash].js`,
assetFileNames: `[name].[hash].[ext]`,
...outputOptions
});
}
finally {
spinner && spinner.stop();
}
if (write && !silent) {
if (emitIndex) {
printFileInfo(indexHtmlPath, indexHtml, 3 /* HTML */);
}
for (const chunk of result.assets) {
if (chunk.type === 'chunk') {
const filePath = path_1.default.join(resolvedAssetsPath, chunk.fileName);
printFileInfo(filePath, chunk.code, 0 /* JS */);
if (chunk.map) {
printFileInfo(filePath + '.map', chunk.map.toString(), 4 /* SOURCE_MAP */);
}
}
else if (emitAssets && chunk.source)
printFileInfo(path_1.default.join(resolvedAssetsPath, chunk.fileName), chunk.source, chunk.fileName.endsWith('.css') ? 1 /* CSS */ : 2 /* ASSET */);
}
}
spinner && spinner.start();
return result;
});
// copy over /public if it exists
if (write && emitAssets && fs_extra_1.default.existsSync(publicDir)) {
for (const file of await fs_extra_1.default.readdir(publicDir)) {
await fs_extra_1.default.copy(path_1.default.join(publicDir, file), path_1.default.resolve(outDir, file));
}
}
spinner && spinner.stop();
if (!silent) {
console.log(`Build completed in ${((Date.now() - start) / 1000).toFixed(2)}s.\n`);
}
// stop the esbuild service after each build
await esbuildService_1.stopService();
return results;
}
exports.build = build;
/**
* Bundles the app in SSR mode.
* - All Vue dependencies are automatically externalized
* - Imports to dependencies are compiled into require() calls
* - Templates are compiled with SSR specific optimizations.
*/
async function ssrBuild(options) {
const { rollupInputOptions, rollupOutputOptions, rollupPluginVueOptions } = options;
return build({
outDir: 'dist-ssr',
...options,
rollupPluginVueOptions: {
...rollupPluginVueOptions,
target: 'node'
},
rollupInputOptions: {
...rollupInputOptions,
external: resolveExternal(rollupInputOptions && rollupInputOptions.external)
},
rollupOutputOptions: {
...rollupOutputOptions,
format: 'cjs',
exports: 'named',
entryFileNames: '[name].js',
// 764 add `Symbol.toStringTag` when build es module into cjs chunk
namespaceToStringTag: true
},
emitIndex: false,
emitAssets: false,
cssCodeSplit: false,
minify: false
});
}
exports.ssrBuild = ssrBuild;
function createEmitPlugin(emitAssets, emit) {
return {
name: 'vite:emit',
async generateBundle(_, output) {
// assume the first asset in `output` is an entry chunk
const assets = Object.values(output);
// process the output before writing
await emit(assets);
// write any assets injected by post-build hooks
for (const asset of assets) {
output[asset.fileName] = asset;
}
// remove assets from bundle if emitAssets is false
if (!emitAssets) {
for (const name in output) {
if (output[name].type === 'asset') {
delete output[name];
}
}
}
}
};
}
/**
* Resolve the output path of `index.html` for the given build (relative to
* `outDir` in Vite config).
*/
function getIndexHtmlOutputPath(build) {
const { input, output } = build;
return input === 'index.html' ? output.file || input : null;
}
function resolveExternal(userExternal) {
const required = ['vue', /^@vue\//];
if (!userExternal) {
return required;
}
if (Array.isArray(userExternal)) {
return [...required, ...userExternal];
}
else if (typeof userExternal === 'function') {
return (src, importer, isResolved) => {
if (src === 'vue' || /^@vue\//.test(src)) {
return true;
}
return userExternal(src, importer, isResolved);
};
}
else {
return [...required, userExternal];
}
}
function printFileInfo(filePath, content, type) {
const needCompression = type === 0 /* JS */ || type === 1 /* CSS */ || type === 3 /* HTML */;
const compressed = needCompression
? `, brotli: ${(require('brotli-size').sync(content) / 1024).toFixed(2)}kb`
: ``;
console.log(`${chalk_1.default.gray(`[write]`)} ${writeColors[type](path_1.default.relative(process.cwd(), filePath))} ${(content.length / 1024).toFixed(2)}kb${compressed}`);
}
//# sourceMappingURL=index.js.map