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.

390 lines
11 KiB

8 months ago
// @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] : [])
]
}