rollup.config.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. // @ts-check
  2. import fs from 'fs'
  3. import path from 'path'
  4. import nodeResolve from '@rollup/plugin-node-resolve'
  5. import typescript from '@rollup/plugin-typescript'
  6. import commonjs from '@rollup/plugin-commonjs'
  7. import json from '@rollup/plugin-json'
  8. import alias from '@rollup/plugin-alias'
  9. import license from 'rollup-plugin-license'
  10. import MagicString from 'magic-string'
  11. import chalk from 'chalk'
  12. import fg from 'fast-glob'
  13. import { sync as resolve } from 'resolve'
  14. /**
  15. * @type { import('rollup').RollupOptions }
  16. */
  17. const envConfig = {
  18. input: path.resolve(__dirname, 'src/client/env.ts'),
  19. plugins: [
  20. typescript({
  21. target: 'es2018',
  22. include: ['src/client/env.ts'],
  23. baseUrl: path.resolve(__dirname, 'src/env'),
  24. paths: {
  25. 'types/*': ['../../types/*']
  26. }
  27. })
  28. ],
  29. output: {
  30. file: path.resolve(__dirname, 'dist/client', 'env.mjs'),
  31. sourcemap: true
  32. }
  33. }
  34. /**
  35. * @type { import('rollup').RollupOptions }
  36. */
  37. const clientConfig = {
  38. input: path.resolve(__dirname, 'src/client/client.ts'),
  39. external: ['./env'],
  40. plugins: [
  41. typescript({
  42. target: 'es2018',
  43. include: ['src/client/**/*.ts'],
  44. baseUrl: path.resolve(__dirname, 'src/client'),
  45. paths: {
  46. 'types/*': ['../../types/*']
  47. }
  48. })
  49. ],
  50. output: {
  51. file: path.resolve(__dirname, 'dist/client', 'client.mjs'),
  52. sourcemap: true
  53. }
  54. }
  55. /**
  56. * @type { import('rollup').RollupOptions }
  57. */
  58. const sharedNodeOptions = {
  59. treeshake: {
  60. moduleSideEffects: 'no-external',
  61. propertyReadSideEffects: false,
  62. tryCatchDeoptimization: false
  63. },
  64. output: {
  65. dir: path.resolve(__dirname, 'dist'),
  66. entryFileNames: `node/[name].js`,
  67. chunkFileNames: 'node/chunks/dep-[hash].js',
  68. exports: 'named',
  69. format: 'cjs',
  70. externalLiveBindings: false,
  71. freeze: false,
  72. sourcemap: true
  73. },
  74. onwarn(warning, warn) {
  75. // node-resolve complains a lot about this but seems to still work?
  76. if (warning.message.includes('Package subpath')) {
  77. return
  78. }
  79. // we use the eval('require') trick to deal with optional deps
  80. if (warning.message.includes('Use of eval')) {
  81. return
  82. }
  83. if (warning.message.includes('Circular dependency')) {
  84. return
  85. }
  86. warn(warning)
  87. }
  88. }
  89. /**
  90. *
  91. * @param {boolean} isProduction
  92. * @returns {import('rollup').RollupOptions}
  93. */
  94. const createNodeConfig = (isProduction) => {
  95. /**
  96. * @type { import('rollup').RollupOptions }
  97. */
  98. const nodeConfig = {
  99. ...sharedNodeOptions,
  100. input: {
  101. index: path.resolve(__dirname, 'src/node/index.ts'),
  102. cli: path.resolve(__dirname, 'src/node/cli.ts')
  103. },
  104. external: [
  105. 'fsevents',
  106. ...Object.keys(require('./package.json').dependencies),
  107. ...(isProduction
  108. ? []
  109. : Object.keys(require('./package.json').devDependencies))
  110. ],
  111. plugins: [
  112. alias({
  113. // packages with "module" field that doesn't play well with cjs bundles
  114. entries: {
  115. '@vue/compiler-dom': require.resolve(
  116. '@vue/compiler-dom/dist/compiler-dom.cjs.js'
  117. ),
  118. 'big.js': require.resolve('big.js/big.js')
  119. }
  120. }),
  121. nodeResolve({ preferBuiltins: true }),
  122. typescript({
  123. target: 'es2019',
  124. include: ['src/**/*.ts', 'types/**'],
  125. exclude: ['src/**/__tests__/**'],
  126. esModuleInterop: true,
  127. // in production we use api-extractor for dts generation
  128. // in development we need to rely on the rollup ts plugin
  129. ...(isProduction
  130. ? {}
  131. : {
  132. tsconfig: 'tsconfig.base.json',
  133. declaration: true,
  134. declarationDir: path.resolve(__dirname, 'dist/')
  135. })
  136. }),
  137. // Some deps have try...catch require of optional deps, but rollup will
  138. // generate code that force require them upfront for side effects.
  139. // Shim them with eval() so rollup can skip these calls.
  140. isProduction &&
  141. shimDepsPlugin({
  142. 'plugins/terser.ts': {
  143. src: `require.resolve('terser'`,
  144. replacement: `require.resolve('vite/dist/node/terser'`
  145. },
  146. // chokidar -> fsevents
  147. 'fsevents-handler.js': {
  148. src: `require('fsevents')`,
  149. replacement: `eval('require')('fsevents')`
  150. },
  151. // cac re-assigns module.exports even in its mjs dist
  152. 'cac/dist/index.mjs': {
  153. src: `if (typeof module !== "undefined") {`,
  154. replacement: `if (false) {`
  155. },
  156. // postcss-import -> sugarss
  157. 'process-content.js': {
  158. src: 'require("sugarss")',
  159. replacement: `eval('require')('sugarss')`
  160. },
  161. 'import-from/index.js': {
  162. pattern: /require\(resolveFrom/g,
  163. replacement: `eval('require')(resolveFrom`
  164. },
  165. 'lilconfig/dist/index.js': {
  166. pattern: /: require,/g,
  167. replacement: `: eval('require'),`
  168. }
  169. }),
  170. commonjs({
  171. extensions: ['.js'],
  172. // Optional peer deps of ws. Native deps that are mostly for performance.
  173. // Since ws is not that perf critical for us, just ignore these deps.
  174. ignore: ['bufferutil', 'utf-8-validate']
  175. }),
  176. json(),
  177. isProduction && licensePlugin()
  178. ]
  179. }
  180. return nodeConfig
  181. }
  182. /**
  183. * Terser needs to be run inside a worker, so it cannot be part of the main
  184. * bundle. We produce a separate bundle for it and shims plugin/terser.ts to
  185. * use the production path during build.
  186. *
  187. * @type { import('rollup').RollupOptions }
  188. */
  189. const terserConfig = {
  190. ...sharedNodeOptions,
  191. output: {
  192. ...sharedNodeOptions.output,
  193. exports: 'default'
  194. },
  195. input: {
  196. terser: require.resolve('terser')
  197. },
  198. plugins: [nodeResolve(), commonjs()]
  199. }
  200. /**
  201. * @type { (deps: Record<string, { src?: string, replacement: string, pattern?: RegExp }>) => import('rollup').Plugin }
  202. */
  203. function shimDepsPlugin(deps) {
  204. const transformed = {}
  205. return {
  206. name: 'shim-deps',
  207. transform(code, id) {
  208. for (const file in deps) {
  209. if (id.replace(/\\/g, '/').endsWith(file)) {
  210. const { src, replacement, pattern } = deps[file]
  211. const magicString = new MagicString(code)
  212. if (src) {
  213. const pos = code.indexOf(src)
  214. if (pos < 0) {
  215. this.error(
  216. `Could not find expected src "${src}" in file "${file}"`
  217. )
  218. }
  219. transformed[file] = true
  220. magicString.overwrite(pos, pos + src.length, replacement)
  221. console.log(`shimmed: ${file}`)
  222. }
  223. if (pattern) {
  224. let match
  225. while ((match = pattern.exec(code))) {
  226. transformed[file] = true
  227. const start = match.index
  228. const end = start + match[0].length
  229. magicString.overwrite(start, end, replacement)
  230. }
  231. if (!transformed[file]) {
  232. this.error(
  233. `Could not find expected pattern "${pattern}" in file "${file}"`
  234. )
  235. }
  236. console.log(`shimmed: ${file}`)
  237. }
  238. return {
  239. code: magicString.toString(),
  240. map: magicString.generateMap({ hires: true })
  241. }
  242. }
  243. }
  244. },
  245. buildEnd(err) {
  246. if (!err) {
  247. for (const file in deps) {
  248. if (!transformed[file]) {
  249. this.error(
  250. `Did not find "${file}" which is supposed to be shimmed, was the file renamed?`
  251. )
  252. }
  253. }
  254. }
  255. }
  256. }
  257. }
  258. function licensePlugin() {
  259. return license({
  260. thirdParty(dependencies) {
  261. // https://github.com/rollup/rollup/blob/master/build-plugins/generate-license-file.js
  262. // MIT Licensed https://github.com/rollup/rollup/blob/master/LICENSE-CORE.md
  263. const coreLicense = fs.readFileSync(
  264. path.resolve(__dirname, '../../LICENSE')
  265. )
  266. function sortLicenses(licenses) {
  267. let withParenthesis = []
  268. let noParenthesis = []
  269. licenses.forEach((license) => {
  270. if (/^\(/.test(license)) {
  271. withParenthesis.push(license)
  272. } else {
  273. noParenthesis.push(license)
  274. }
  275. })
  276. withParenthesis = withParenthesis.sort()
  277. noParenthesis = noParenthesis.sort()
  278. return [...noParenthesis, ...withParenthesis]
  279. }
  280. const licenses = new Set()
  281. const dependencyLicenseTexts = dependencies
  282. .sort(({ name: nameA }, { name: nameB }) =>
  283. nameA > nameB ? 1 : nameB > nameA ? -1 : 0
  284. )
  285. .map(
  286. ({
  287. name,
  288. license,
  289. licenseText,
  290. author,
  291. maintainers,
  292. contributors,
  293. repository
  294. }) => {
  295. let text = `## ${name}\n`
  296. if (license) {
  297. text += `License: ${license}\n`
  298. }
  299. const names = new Set()
  300. if (author && author.name) {
  301. names.add(author.name)
  302. }
  303. for (const person of maintainers.concat(contributors)) {
  304. if (person && person.name) {
  305. names.add(person.name)
  306. }
  307. }
  308. if (names.size > 0) {
  309. text += `By: ${Array.from(names).join(', ')}\n`
  310. }
  311. if (repository) {
  312. text += `Repository: ${repository.url || repository}\n`
  313. }
  314. if (!licenseText) {
  315. try {
  316. const pkgDir = path.dirname(
  317. resolve(path.join(name, 'package.json'), {
  318. preserveSymlinks: false
  319. })
  320. )
  321. const licenseFile = fg.sync(`${pkgDir}/LICENSE*`, {
  322. caseSensitiveMatch: false
  323. })[0]
  324. if (licenseFile) {
  325. licenseText = fs.readFileSync(licenseFile, 'utf-8')
  326. }
  327. } catch {}
  328. }
  329. if (licenseText) {
  330. text +=
  331. '\n' +
  332. licenseText
  333. .trim()
  334. .replace(/(\r\n|\r)/gm, '\n')
  335. .split('\n')
  336. .map((line) => `> ${line}`)
  337. .join('\n') +
  338. '\n'
  339. }
  340. licenses.add(license)
  341. return text
  342. }
  343. )
  344. .join('\n---------------------------------------\n\n')
  345. const licenseText =
  346. `# Vite core license\n` +
  347. `Vite is released under the MIT license:\n\n` +
  348. coreLicense +
  349. `\n# Licenses of bundled dependencies\n` +
  350. `The published Vite artifact additionally contains code with the following licenses:\n` +
  351. `${sortLicenses(licenses).join(', ')}\n\n` +
  352. `# Bundled dependencies:\n` +
  353. dependencyLicenseTexts
  354. const existingLicenseText = fs.readFileSync('LICENSE.md', 'utf8')
  355. if (existingLicenseText !== licenseText) {
  356. fs.writeFileSync('LICENSE.md', licenseText)
  357. console.warn(
  358. chalk.yellow(
  359. '\nLICENSE.md updated. You should commit the updated file.\n'
  360. )
  361. )
  362. }
  363. }
  364. })
  365. }
  366. export default (commandLineArgs) => {
  367. const isDev = commandLineArgs.watch
  368. const isProduction = !isDev
  369. return [
  370. envConfig,
  371. clientConfig,
  372. createNodeConfig(isProduction),
  373. ...(isProduction ? [terserConfig] : [])
  374. ]
  375. }