From 2a45ed77b49f0d511538f874b4c2357537157b8a Mon Sep 17 00:00:00 2001 From: likui <2218301630@qq.com> Date: Tue, 5 May 2020 20:37:50 +0800 Subject: [PATCH 1/5] feat: add asset options into build options --- src/node/build.ts | 6 ++++-- src/node/buildPluginAsset.ts | 27 ++++++++++++++++++++------- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/node/build.ts b/src/node/build.ts index d8eb9e66557d76..d97042ba313e18 100644 --- a/src/node/build.ts +++ b/src/node/build.ts @@ -14,7 +14,7 @@ import { Options } from 'rollup-plugin-vue' import { createBuildResolvePlugin } from './buildPluginResolve' import { createBuildHtmlPlugin, scriptRE } from './buildPluginHtml' import { createBuildCssPlugin } from './buildPluginCss' -import { createBuildAssetPlugin } from './buildPluginAsset' +import { AssetOptions, createBuildAssetPlugin } from './buildPluginAsset' import { isExternalUrl } from './utils' export interface BuildOptions { @@ -23,6 +23,7 @@ export interface BuildOptions { resolvers?: Resolver[] outDir?: string assetsDir?: string + assetOptions?: AssetOptions // list files that are included in the build, but not inside project root. srcRoots?: string[] rollupInputOptions?: InputOptions @@ -48,6 +49,7 @@ export async function build(options: BuildOptions = {}): Promise { cdn = !resolveVue(root).hasLocalVue, outDir = path.resolve(root, 'dist'), assetsDir = 'assets', + assetOptions = {}, resolvers = [], srcRoots = [], rollupInputOptions = {}, @@ -111,7 +113,7 @@ export async function build(options: BuildOptions = {}): Promise { // vite:css createBuildCssPlugin(root, assetsDir, cssFileName, minify), // vite:asset - createBuildAssetPlugin(assetsDir), + createBuildAssetPlugin(assetsDir, assetOptions), // minify with terser // modules: true and toplevel: true are implied with format: 'es' ...(minify ? [require('rollup-plugin-terser').terser()] : []) diff --git a/src/node/buildPluginAsset.ts b/src/node/buildPluginAsset.ts index 4b4e962b6512c3..44c593336f8882 100644 --- a/src/node/buildPluginAsset.ts +++ b/src/node/buildPluginAsset.ts @@ -8,10 +8,19 @@ import mime from 'mime-types' const debug = require('debug')('vite:build:asset') -// TODO make this configurable -const inlineThreshold = 4096 +export interface AssetOptions { + inlineThreshold?: number +} + +const defaultAssetOptions: AssetOptions = { + inlineThreshold: 4096 +} -export const getAssetPublicPath = async (id: string, assetsDir: string) => { +export const getAssetPublicPath = async ( + id: string, + assetsDir: string, + assetOptions: AssetOptions +) => { const ext = path.extname(id) const baseName = path.basename(id, ext) const resolvedFileName = `${baseName}.${hash_sum(id)}${ext}` @@ -19,7 +28,7 @@ export const getAssetPublicPath = async (id: string, assetsDir: string) => { let url = slash(path.join('/', assetsDir, resolvedFileName)) const content = await fs.readFile(id) if (!id.endsWith(`.svg`)) { - if (content.length < inlineThreshold) { + if (content.length < assetOptions.inlineThreshold!) { url = `data:${mime.lookup(id)};base64,${content.toString('base64')}` } } @@ -45,16 +54,20 @@ export const registerAssets = ( } } -export const createBuildAssetPlugin = (assetsDir: string): Plugin => { +export const createBuildAssetPlugin = ( + assetsDir: string, + assetOptions: AssetOptions +): Plugin => { const assets = new Map() - + assetOptions = { ...defaultAssetOptions, ...assetOptions } return { name: 'vite:asset', async load(id) { if (isStaticAsset(id)) { const { fileName, content, url } = await getAssetPublicPath( id, - assetsDir + assetsDir, + assetOptions ) assets.set(fileName, content) debug(`${id} -> ${url}`) From e786985aa6bfd876b1c7e2dc0dd5c766afc542e8 Mon Sep 17 00:00:00 2001 From: likui <2218301630@qq.com> Date: Tue, 5 May 2020 20:44:32 +0800 Subject: [PATCH 2/5] feat: add miss plugin function --- src/node/build.ts | 2 +- src/node/buildPluginCss.ts | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/node/build.ts b/src/node/build.ts index d97042ba313e18..3dd4b0091c67cf 100644 --- a/src/node/build.ts +++ b/src/node/build.ts @@ -111,7 +111,7 @@ export async function build(options: BuildOptions = {}): Promise { __DEV__: 'false' }), // vite:css - createBuildCssPlugin(root, assetsDir, cssFileName, minify), + createBuildCssPlugin(root, assetsDir, cssFileName, minify, assetOptions), // vite:asset createBuildAssetPlugin(assetsDir, assetOptions), // minify with terser diff --git a/src/node/buildPluginCss.ts b/src/node/buildPluginCss.ts index 2eff224b8e8d10..f698f152aad6e7 100644 --- a/src/node/buildPluginCss.ts +++ b/src/node/buildPluginCss.ts @@ -1,6 +1,10 @@ import path from 'path' import { Plugin } from 'rollup' -import { getAssetPublicPath, registerAssets } from './buildPluginAsset' +import { + AssetOptions, + getAssetPublicPath, + registerAssets +} from './buildPluginAsset' import { loadPostcssConfig } from './config' import { isExternalUrl } from './utils' @@ -12,7 +16,8 @@ export const createBuildCssPlugin = ( root: string, assetsDir: string, cssFileName: string, - minify: boolean + minify: boolean, + assetOptions: AssetOptions ): Plugin => { const styles: Map = new Map() const assets = new Map() @@ -40,7 +45,8 @@ export const createBuildCssPlugin = ( const file = path.join(fileDir, rawUrl) const { fileName, content, url } = await getAssetPublicPath( file, - assetsDir + assetsDir, + assetOptions ) assets.set(fileName, content) debug(`url(${rawUrl}) -> url(${url})`) From 973b3fa2629c43aaeb6f208a2e624a53e38a2cae Mon Sep 17 00:00:00 2001 From: likui <2218301630@qq.com> Date: Tue, 5 May 2020 22:41:25 +0800 Subject: [PATCH 3/5] feat: rename build `assetsOptions` option --- src/node/build.ts | 10 +++++----- src/node/buildPluginAsset.ts | 14 +++++++------- src/node/buildPluginCss.ts | 6 +++--- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/node/build.ts b/src/node/build.ts index 3dd4b0091c67cf..b23620849e3b53 100644 --- a/src/node/build.ts +++ b/src/node/build.ts @@ -14,7 +14,7 @@ import { Options } from 'rollup-plugin-vue' import { createBuildResolvePlugin } from './buildPluginResolve' import { createBuildHtmlPlugin, scriptRE } from './buildPluginHtml' import { createBuildCssPlugin } from './buildPluginCss' -import { AssetOptions, createBuildAssetPlugin } from './buildPluginAsset' +import { AssetsOptions, createBuildAssetPlugin } from './buildPluginAsset' import { isExternalUrl } from './utils' export interface BuildOptions { @@ -23,7 +23,7 @@ export interface BuildOptions { resolvers?: Resolver[] outDir?: string assetsDir?: string - assetOptions?: AssetOptions + assetsOptions?: AssetsOptions // list files that are included in the build, but not inside project root. srcRoots?: string[] rollupInputOptions?: InputOptions @@ -49,7 +49,7 @@ export async function build(options: BuildOptions = {}): Promise { cdn = !resolveVue(root).hasLocalVue, outDir = path.resolve(root, 'dist'), assetsDir = 'assets', - assetOptions = {}, + assetsOptions = {}, resolvers = [], srcRoots = [], rollupInputOptions = {}, @@ -111,9 +111,9 @@ export async function build(options: BuildOptions = {}): Promise { __DEV__: 'false' }), // vite:css - createBuildCssPlugin(root, assetsDir, cssFileName, minify, assetOptions), + createBuildCssPlugin(root, assetsDir, cssFileName, minify, assetsOptions), // vite:asset - createBuildAssetPlugin(assetsDir, assetOptions), + createBuildAssetPlugin(assetsDir, assetsOptions), // minify with terser // modules: true and toplevel: true are implied with format: 'es' ...(minify ? [require('rollup-plugin-terser').terser()] : []) diff --git a/src/node/buildPluginAsset.ts b/src/node/buildPluginAsset.ts index 44c593336f8882..76a43ab63b08bb 100644 --- a/src/node/buildPluginAsset.ts +++ b/src/node/buildPluginAsset.ts @@ -8,18 +8,18 @@ import mime from 'mime-types' const debug = require('debug')('vite:build:asset') -export interface AssetOptions { +export interface AssetsOptions { inlineThreshold?: number } -const defaultAssetOptions: AssetOptions = { +const defaultAssetOptions: AssetsOptions = { inlineThreshold: 4096 } export const getAssetPublicPath = async ( id: string, assetsDir: string, - assetOptions: AssetOptions + assetsOptions: AssetsOptions ) => { const ext = path.extname(id) const baseName = path.basename(id, ext) @@ -28,7 +28,7 @@ export const getAssetPublicPath = async ( let url = slash(path.join('/', assetsDir, resolvedFileName)) const content = await fs.readFile(id) if (!id.endsWith(`.svg`)) { - if (content.length < assetOptions.inlineThreshold!) { + if (content.length < assetsOptions.inlineThreshold!) { url = `data:${mime.lookup(id)};base64,${content.toString('base64')}` } } @@ -56,10 +56,10 @@ export const registerAssets = ( export const createBuildAssetPlugin = ( assetsDir: string, - assetOptions: AssetOptions + assetsOptions: AssetsOptions ): Plugin => { const assets = new Map() - assetOptions = { ...defaultAssetOptions, ...assetOptions } + assetsOptions = { ...defaultAssetOptions, ...assetsOptions } return { name: 'vite:asset', async load(id) { @@ -67,7 +67,7 @@ export const createBuildAssetPlugin = ( const { fileName, content, url } = await getAssetPublicPath( id, assetsDir, - assetOptions + assetsOptions ) assets.set(fileName, content) debug(`${id} -> ${url}`) diff --git a/src/node/buildPluginCss.ts b/src/node/buildPluginCss.ts index f698f152aad6e7..d92500431804c5 100644 --- a/src/node/buildPluginCss.ts +++ b/src/node/buildPluginCss.ts @@ -1,7 +1,7 @@ import path from 'path' import { Plugin } from 'rollup' import { - AssetOptions, + AssetsOptions, getAssetPublicPath, registerAssets } from './buildPluginAsset' @@ -17,7 +17,7 @@ export const createBuildCssPlugin = ( assetsDir: string, cssFileName: string, minify: boolean, - assetOptions: AssetOptions + assetsOptions: AssetsOptions ): Plugin => { const styles: Map = new Map() const assets = new Map() @@ -46,7 +46,7 @@ export const createBuildCssPlugin = ( const { fileName, content, url } = await getAssetPublicPath( file, assetsDir, - assetOptions + assetsOptions ) assets.set(fileName, content) debug(`url(${rawUrl}) -> url(${url})`) From aaa9f32c9073789ebdc523f6f260101433d8d473 Mon Sep 17 00:00:00 2001 From: likui <2218301630@qq.com> Date: Tue, 5 May 2020 23:00:22 +0800 Subject: [PATCH 4/5] chore: resolve conflict --- src/node/build.ts | 167 ++++++++++++++++++++++++++++++----- src/node/buildPluginAsset.ts | 4 +- 2 files changed, 148 insertions(+), 23 deletions(-) diff --git a/src/node/build.ts b/src/node/build.ts index b23620849e3b53..3a921b3023c33e 100644 --- a/src/node/build.ts +++ b/src/node/build.ts @@ -4,7 +4,8 @@ import { rollup as Rollup, InputOptions, OutputOptions, - RollupOutput + RollupOutput, + ExternalOption } from 'rollup' import { resolveVue } from './vueResolver' import resolve from 'resolve-from' @@ -18,20 +19,63 @@ import { AssetsOptions, createBuildAssetPlugin } from './buildPluginAsset' import { isExternalUrl } from './utils' export interface BuildOptions { + /** + * Project root path on file system. + */ root?: string + /** + * If true, will be importing Vue from a CDN. + * Dsiabled automatically when a local vue installation is present. + */ cdn?: boolean + /** + * Resolvers to map dev server public path requests to/from file system paths, + * and optionally map module ids to public path requests. + */ resolvers?: Resolver[] + /** + * Defaults to `dist` + */ outDir?: string + /** + * Nest js / css / static assets under a directory under `outDir`. + * Defaults to `assets` + */ assetsDir?: string + /** + * The option with process assets. eg.image + */ assetsOptions?: AssetsOptions - // list files that are included in the build, but not inside project root. + /** + * List files that are included in the build, but not inside project root. + * e.g. if you are building a higher level tool on top of vite and includes + * some code that will be bundled into the final build. + */ srcRoots?: string[] + /** + * Will be passed to rollup.rollup() + */ rollupInputOptions?: InputOptions + /** + * Will be passed to bundle.generate() + */ rollupOutputOptions?: OutputOptions rollupPluginVueOptions?: Partial + /** + * Whether to emit assets other than JavaScript + */ emitAssets?: boolean - write?: boolean // if false, does not write to disk. + /** + * Whether to write bundle to disk + */ + write?: boolean + /** + * Whether to minify output + */ minify?: boolean + /** + * Whether to log asset info to console + */ silent?: boolean } @@ -40,6 +84,24 @@ export interface BuildResult { assets: RollupOutput['output'] } +const enum WriteType { + JS, + CSS, + ASSET, + HTML +} + +const writeColors = { + [WriteType.JS]: chalk.cyan, + [WriteType.CSS]: chalk.magenta, + [WriteType.ASSET]: chalk.green, + [WriteType.HTML]: chalk.blue +} + +/** + * Bundles the app for production. + * Returns a Promise containing the build result. + */ export async function build(options: BuildOptions = {}): Promise { process.env.NODE_ENV = 'production' const start = Date.now() @@ -68,6 +130,23 @@ export async function build(options: BuildOptions = {}): Promise { const cssFileName = 'style.css' const resolvedAssetsPath = path.join(outDir, assetsDir) + const cwd = process.cwd() + const writeFile = async ( + filepath: string, + content: string | Uint8Array, + type: WriteType + ) => { + await fs.ensureDir(path.dirname(filepath)) + await fs.writeFile(filepath, content) + if (!silent) { + console.log( + `${chalk.gray(`[write]`)} ${writeColors[type]( + path.relative(cwd, filepath) + )} ${(content.length / 1024).toFixed(2)}kb` + ) + } + } + let indexContent: string | null = null try { indexContent = await fs.readFile(indexPath, 'utf-8') @@ -156,7 +235,7 @@ export async function build(options: BuildOptions = {}): Promise { } } - // TODO handle public path for injections? + // TODO handle base path for injections? // this would also affect paths in templates and css. if (generatedIndex) { // inject css link @@ -182,22 +261,16 @@ export async function build(options: BuildOptions = {}): Promise { // write chunk if (write) { const filepath = path.join(resolvedAssetsPath, chunk.fileName) - !silent && - console.log( - `write ${chalk.cyan(path.relative(process.cwd(), filepath))}` - ) - await fs.ensureDir(path.dirname(filepath)) - await fs.writeFile(filepath, chunk.code) + await writeFile(filepath, chunk.code, WriteType.JS) } } else if (emitAssets && write) { // write asset const filepath = path.join(resolvedAssetsPath, chunk.fileName) - !silent && - console.log( - `write ${chalk.magenta(path.relative(process.cwd(), filepath))}` - ) - await fs.ensureDir(path.dirname(filepath)) - await fs.writeFile(filepath, chunk.source) + await writeFile( + filepath, + chunk.source, + chunk.fileName.endsWith('.css') ? WriteType.CSS : WriteType.ASSET + ) } } @@ -205,11 +278,7 @@ export async function build(options: BuildOptions = {}): Promise { // write html if (generatedIndex) { const indexOutPath = path.join(outDir, 'index.html') - !silent && - console.log( - `write ${chalk.green(path.relative(process.cwd(), indexOutPath))}` - ) - await fs.writeFile(indexOutPath, generatedIndex) + await writeFile(indexOutPath, generatedIndex, WriteType.HTML) } } @@ -223,3 +292,59 @@ export async function build(options: BuildOptions = {}): Promise { html: generatedIndex || '' } } + +/** + * 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. + */ +export async function ssrBuild( + options: BuildOptions = {} +): Promise { + const { + rollupInputOptions, + rollupOutputOptions, + rollupPluginVueOptions + } = options + + return build({ + ...options, + rollupPluginVueOptions: { + ...rollupPluginVueOptions, + target: 'node' + }, + rollupInputOptions: { + ...rollupInputOptions, + external: resolveExternal( + rollupInputOptions && rollupInputOptions.external + ) + }, + rollupOutputOptions: { + ...rollupOutputOptions, + format: 'cjs', + exports: 'named' + } + }) +} + +function resolveExternal( + userExternal: ExternalOption | undefined +): ExternalOption { + 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] + } +} diff --git a/src/node/buildPluginAsset.ts b/src/node/buildPluginAsset.ts index 76a43ab63b08bb..241542155fe310 100644 --- a/src/node/buildPluginAsset.ts +++ b/src/node/buildPluginAsset.ts @@ -12,7 +12,7 @@ export interface AssetsOptions { inlineThreshold?: number } -const defaultAssetOptions: AssetsOptions = { +const defaultAssetsOptions: AssetsOptions = { inlineThreshold: 4096 } @@ -59,7 +59,7 @@ export const createBuildAssetPlugin = ( assetsOptions: AssetsOptions ): Plugin => { const assets = new Map() - assetsOptions = { ...defaultAssetOptions, ...assetsOptions } + assetsOptions = { ...defaultAssetsOptions, ...assetsOptions } return { name: 'vite:asset', async load(id) { From bd07f27bfeea316b4a77666b80d30200d7da9b87 Mon Sep 17 00:00:00 2001 From: likui <2218301630@qq.com> Date: Tue, 5 May 2020 23:03:11 +0800 Subject: [PATCH 5/5] chore: resolve conflict --- src/node/build.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/node/build.ts b/src/node/build.ts index 8b9e1fe1343135..3a921b3023c33e 100644 --- a/src/node/build.ts +++ b/src/node/build.ts @@ -15,7 +15,7 @@ import { Options } from 'rollup-plugin-vue' import { createBuildResolvePlugin } from './buildPluginResolve' import { createBuildHtmlPlugin, scriptRE } from './buildPluginHtml' import { createBuildCssPlugin } from './buildPluginCss' -import { createBuildAssetPlugin } from './buildPluginAsset' +import { AssetsOptions, createBuildAssetPlugin } from './buildPluginAsset' import { isExternalUrl } from './utils' export interface BuildOptions { @@ -42,6 +42,10 @@ export interface BuildOptions { * Defaults to `assets` */ assetsDir?: string + /** + * The option with process assets. eg.image + */ + assetsOptions?: AssetsOptions /** * List files that are included in the build, but not inside project root. * e.g. if you are building a higher level tool on top of vite and includes @@ -107,6 +111,7 @@ export async function build(options: BuildOptions = {}): Promise { cdn = !resolveVue(root).hasLocalVue, outDir = path.resolve(root, 'dist'), assetsDir = 'assets', + assetsOptions = {}, resolvers = [], srcRoots = [], rollupInputOptions = {}, @@ -185,9 +190,9 @@ export async function build(options: BuildOptions = {}): Promise { __DEV__: 'false' }), // vite:css - createBuildCssPlugin(root, assetsDir, cssFileName, minify), + createBuildCssPlugin(root, assetsDir, cssFileName, minify, assetsOptions), // vite:asset - createBuildAssetPlugin(assetsDir), + createBuildAssetPlugin(assetsDir, assetsOptions), // minify with terser // modules: true and toplevel: true are implied with format: 'es' ...(minify ? [require('rollup-plugin-terser').terser()] : [])