You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
389 lines
11 KiB
389 lines
11 KiB
// @ts-check |
|
import fs from 'fs' |
|
import path from 'path' |
|
import nodeResolve from '@rollup/plugin-node-resolve' |
|
import typescript from '@rollup/plugin-typescript' |
|
import commonjs from '@rollup/plugin-commonjs' |
|
import json from '@rollup/plugin-json' |
|
import alias from '@rollup/plugin-alias' |
|
import license from 'rollup-plugin-license' |
|
import MagicString from 'magic-string' |
|
import chalk from 'chalk' |
|
import fg from 'fast-glob' |
|
import { sync as resolve } from 'resolve' |
|
|
|
/** |
|
* @type { import('rollup').RollupOptions } |
|
*/ |
|
const envConfig = { |
|
input: path.resolve(__dirname, 'src/client/env.ts'), |
|
plugins: [ |
|
typescript({ |
|
target: 'es2018', |
|
include: ['src/client/env.ts'], |
|
baseUrl: path.resolve(__dirname, 'src/env'), |
|
paths: { |
|
'types/*': ['../../types/*'] |
|
} |
|
}) |
|
], |
|
output: { |
|
file: path.resolve(__dirname, 'dist/client', 'env.mjs'), |
|
sourcemap: true |
|
} |
|
} |
|
|
|
/** |
|
* @type { import('rollup').RollupOptions } |
|
*/ |
|
const clientConfig = { |
|
input: path.resolve(__dirname, 'src/client/client.ts'), |
|
external: ['./env'], |
|
plugins: [ |
|
typescript({ |
|
target: 'es2018', |
|
include: ['src/client/**/*.ts'], |
|
baseUrl: path.resolve(__dirname, 'src/client'), |
|
paths: { |
|
'types/*': ['../../types/*'] |
|
} |
|
}) |
|
], |
|
output: { |
|
file: path.resolve(__dirname, 'dist/client', 'client.mjs'), |
|
sourcemap: true |
|
} |
|
} |
|
|
|
/** |
|
* @type { import('rollup').RollupOptions } |
|
*/ |
|
const sharedNodeOptions = { |
|
treeshake: { |
|
moduleSideEffects: 'no-external', |
|
propertyReadSideEffects: false, |
|
tryCatchDeoptimization: false |
|
}, |
|
output: { |
|
dir: path.resolve(__dirname, 'dist'), |
|
entryFileNames: `node/[name].js`, |
|
chunkFileNames: 'node/chunks/dep-[hash].js', |
|
exports: 'named', |
|
format: 'cjs', |
|
externalLiveBindings: false, |
|
freeze: false, |
|
sourcemap: true |
|
}, |
|
onwarn(warning, warn) { |
|
// node-resolve complains a lot about this but seems to still work? |
|
if (warning.message.includes('Package subpath')) { |
|
return |
|
} |
|
// we use the eval('require') trick to deal with optional deps |
|
if (warning.message.includes('Use of eval')) { |
|
return |
|
} |
|
if (warning.message.includes('Circular dependency')) { |
|
return |
|
} |
|
warn(warning) |
|
} |
|
} |
|
|
|
/** |
|
* |
|
* @param {boolean} isProduction |
|
* @returns {import('rollup').RollupOptions} |
|
*/ |
|
const createNodeConfig = (isProduction) => { |
|
/** |
|
* @type { import('rollup').RollupOptions } |
|
*/ |
|
const nodeConfig = { |
|
...sharedNodeOptions, |
|
input: { |
|
index: path.resolve(__dirname, 'src/node/index.ts'), |
|
cli: path.resolve(__dirname, 'src/node/cli.ts') |
|
}, |
|
external: [ |
|
'fsevents', |
|
...Object.keys(require('./package.json').dependencies), |
|
...(isProduction |
|
? [] |
|
: Object.keys(require('./package.json').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' |
|
), |
|
'big.js': require.resolve('big.js/big.js') |
|
} |
|
}), |
|
nodeResolve({ preferBuiltins: true }), |
|
typescript({ |
|
target: 'es2019', |
|
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 |
|
? {} |
|
: { |
|
tsconfig: 'tsconfig.base.json', |
|
declaration: true, |
|
declarationDir: path.resolve(__dirname, 'dist/') |
|
}) |
|
}), |
|
// 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')` |
|
}, |
|
'import-from/index.js': { |
|
pattern: /require\(resolveFrom/g, |
|
replacement: `eval('require')(resolveFrom` |
|
}, |
|
'lilconfig/dist/index.js': { |
|
pattern: /: require,/g, |
|
replacement: `: eval('require'),` |
|
} |
|
}), |
|
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 |
|
} |
|
|
|
/** |
|
* 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 = { |
|
...sharedNodeOptions, |
|
output: { |
|
...sharedNodeOptions.output, |
|
exports: 'default' |
|
}, |
|
input: { |
|
terser: require.resolve('terser') |
|
}, |
|
plugins: [nodeResolve(), commonjs()] |
|
} |
|
|
|
/** |
|
* @type { (deps: Record<string, { src?: string, replacement: string, pattern?: RegExp }>) => import('rollup').Plugin } |
|
*/ |
|
function shimDepsPlugin(deps) { |
|
const transformed = {} |
|
|
|
return { |
|
name: 'shim-deps', |
|
transform(code, id) { |
|
for (const file in deps) { |
|
if (id.replace(/\\/g, '/').endsWith(file)) { |
|
const { src, replacement, pattern } = deps[file] |
|
|
|
const magicString = new MagicString(code) |
|
if (src) { |
|
const pos = code.indexOf(src) |
|
if (pos < 0) { |
|
this.error( |
|
`Could not find expected src "${src}" in file "${file}"` |
|
) |
|
} |
|
transformed[file] = true |
|
magicString.overwrite(pos, pos + src.length, replacement) |
|
console.log(`shimmed: ${file}`) |
|
} |
|
|
|
if (pattern) { |
|
let match |
|
while ((match = pattern.exec(code))) { |
|
transformed[file] = true |
|
const start = match.index |
|
const end = start + match[0].length |
|
magicString.overwrite(start, end, replacement) |
|
} |
|
if (!transformed[file]) { |
|
this.error( |
|
`Could not find expected pattern "${pattern}" in file "${file}"` |
|
) |
|
} |
|
console.log(`shimmed: ${file}`) |
|
} |
|
|
|
return { |
|
code: magicString.toString(), |
|
map: magicString.generateMap({ hires: true }) |
|
} |
|
} |
|
} |
|
}, |
|
buildEnd(err) { |
|
if (!err) { |
|
for (const file in deps) { |
|
if (!transformed[file]) { |
|
this.error( |
|
`Did not find "${file}" which is supposed to be shimmed, was the file renamed?` |
|
) |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
function licensePlugin() { |
|
return license({ |
|
thirdParty(dependencies) { |
|
// https://github.com/rollup/rollup/blob/master/build-plugins/generate-license-file.js |
|
// MIT Licensed https://github.com/rollup/rollup/blob/master/LICENSE-CORE.md |
|
const coreLicense = fs.readFileSync( |
|
path.resolve(__dirname, '../../LICENSE') |
|
) |
|
function sortLicenses(licenses) { |
|
let withParenthesis = [] |
|
let noParenthesis = [] |
|
licenses.forEach((license) => { |
|
if (/^\(/.test(license)) { |
|
withParenthesis.push(license) |
|
} else { |
|
noParenthesis.push(license) |
|
} |
|
}) |
|
withParenthesis = withParenthesis.sort() |
|
noParenthesis = noParenthesis.sort() |
|
return [...noParenthesis, ...withParenthesis] |
|
} |
|
const licenses = new Set() |
|
const dependencyLicenseTexts = dependencies |
|
.sort(({ name: nameA }, { name: nameB }) => |
|
nameA > nameB ? 1 : nameB > nameA ? -1 : 0 |
|
) |
|
.map( |
|
({ |
|
name, |
|
license, |
|
licenseText, |
|
author, |
|
maintainers, |
|
contributors, |
|
repository |
|
}) => { |
|
let text = `## ${name}\n` |
|
if (license) { |
|
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) |
|
} |
|
} |
|
if (names.size > 0) { |
|
text += `By: ${Array.from(names).join(', ')}\n` |
|
} |
|
if (repository) { |
|
text += `Repository: ${repository.url || repository}\n` |
|
} |
|
if (!licenseText) { |
|
try { |
|
const pkgDir = path.dirname( |
|
resolve(path.join(name, 'package.json'), { |
|
preserveSymlinks: false |
|
}) |
|
) |
|
const licenseFile = fg.sync(`${pkgDir}/LICENSE*`, { |
|
caseSensitiveMatch: false |
|
})[0] |
|
if (licenseFile) { |
|
licenseText = fs.readFileSync(licenseFile, 'utf-8') |
|
} |
|
} catch {} |
|
} |
|
if (licenseText) { |
|
text += |
|
'\n' + |
|
licenseText |
|
.trim() |
|
.replace(/(\r\n|\r)/gm, '\n') |
|
.split('\n') |
|
.map((line) => `> ${line}`) |
|
.join('\n') + |
|
'\n' |
|
} |
|
licenses.add(license) |
|
return text |
|
} |
|
) |
|
.join('\n---------------------------------------\n\n') |
|
const licenseText = |
|
`# Vite core license\n` + |
|
`Vite is released under the MIT license:\n\n` + |
|
coreLicense + |
|
`\n# Licenses of bundled dependencies\n` + |
|
`The published Vite artifact additionally contains code with the following licenses:\n` + |
|
`${sortLicenses(licenses).join(', ')}\n\n` + |
|
`# Bundled dependencies:\n` + |
|
dependencyLicenseTexts |
|
const existingLicenseText = fs.readFileSync('LICENSE.md', 'utf8') |
|
if (existingLicenseText !== licenseText) { |
|
fs.writeFileSync('LICENSE.md', licenseText) |
|
console.warn( |
|
chalk.yellow( |
|
'\nLICENSE.md updated. You should commit the updated file.\n' |
|
) |
|
) |
|
} |
|
} |
|
}) |
|
} |
|
|
|
export default (commandLineArgs) => { |
|
const isDev = commandLineArgs.watch |
|
const isProduction = !isDev |
|
|
|
return [ |
|
envConfig, |
|
clientConfig, |
|
createNodeConfig(isProduction), |
|
...(isProduction ? [terserConfig] : []) |
|
] |
|
}
|
|
|