Skip to content

Commit

Permalink
refactor(build): refactor assets generate (#957)
Browse files Browse the repository at this point in the history
1. generate asset by `emitFile` and format file name by rollup options.
2. use `rollup.write` instead of `rollup.generate`, let many rollup plugins can work with `writeBundle` hook.

fix #880, fix #912, fix #378
  • Loading branch information
underfin authored Oct 26, 2020
1 parent 468a085 commit 21b4aa7
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 79 deletions.
41 changes: 24 additions & 17 deletions src/node/build/buildPluginAsset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import path from 'path'
import fs from 'fs-extra'
import { Plugin, OutputBundle } from 'rollup'
import { cleanUrl } from '../utils'
import hash_sum from 'hash-sum'
import slash from 'slash'
import mime from 'mime-types'
import { InternalResolver } from '../resolver'
Expand All @@ -12,11 +11,12 @@ const debug = require('debug')('vite:build:asset')
interface AssetCacheEntry {
content?: Buffer
fileName?: string
url: string
url: string | undefined
}

const assetResolveCache = new Map<string, AssetCacheEntry>()
const publicDirRE = /^public(\/|\\)/
export const injectAssetRe = /import.meta.ROLLUP_FILE_URL_(\w+)/

export const resolveAsset = async (
id: string,
Expand Down Expand Up @@ -55,11 +55,7 @@ export const resolveAsset = async (
}

if (!resolved) {
const ext = path.extname(id)
const baseName = path.basename(id, ext)
const resolvedFileName = `${baseName}.${hash_sum(id)}${ext}`

let url = publicBase + slash(path.join(assetsDir, resolvedFileName))
let url: string | undefined
let content: Buffer | undefined = await fs.readFile(id)
if (!id.endsWith(`.svg`) && content.length < Number(inlineLimit)) {
url = `data:${mime.lookup(id)};base64,${content.toString('base64')}`
Expand All @@ -68,7 +64,7 @@ export const resolveAsset = async (

resolved = {
content,
fileName: resolvedFileName,
fileName: path.basename(id),
url
}
}
Expand Down Expand Up @@ -97,31 +93,42 @@ export const createBuildAssetPlugin = (
resolver: InternalResolver,
publicBase: string,
assetsDir: string,
inlineLimit: number
inlineLimit: number,
emitAssets: boolean
): Plugin => {
const assets = new Map<string, Buffer>()

return {
name: 'vite:asset',
async load(id) {
if (resolver.isAssetRequest(id)) {
const { fileName, content, url } = await resolveAsset(
let { fileName, content, url } = await resolveAsset(
id,
root,
publicBase,
assetsDir,
inlineLimit
)
if (fileName && content) {
assets.set(fileName, content)
if (!url && emitAssets && fileName && content) {
url =
'import.meta.ROLLUP_FILE_URL_' +
this.emitFile({
name: fileName,
type: 'asset',
source: content
})
}
debug(`${id} -> ${url.startsWith('data:') ? `base64 inlined` : url}`)
debug(`${id} -> ${url!.startsWith('data:') ? `base64 inlined` : id}`)
return `export default ${JSON.stringify(url)}`
}
},

generateBundle(_options, bundle) {
registerAssets(assets, bundle)
async renderChunk(code) {
let match
while ((match = injectAssetRe.exec(code))) {
const outputFilepath =
publicBase + slash(path.join(assetsDir, this.getFileName(match[1])))
code = code.replace(match[0], outputFilepath)
}
return { code, map: null }
}
}
}
52 changes: 32 additions & 20 deletions src/node/build/buildPluginCss.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import path from 'path'
import { Plugin } from 'rollup'
import { resolveAsset, registerAssets } from './buildPluginAsset'
import { resolveAsset, injectAssetRe } from './buildPluginAsset'
import { BuildConfig } from '../config'
import hash_sum from 'hash-sum'
import {
urlRE,
compileCss,
Expand All @@ -18,6 +17,7 @@ import {
import chalk from 'chalk'
import { CssPreprocessOptions } from '../config'
import { dataToEsm } from '@rollup/pluginutils'
import slash from 'slash'

const debug = require('debug')('vite:build:css')

Expand All @@ -33,6 +33,7 @@ interface BuildCssOption {
cssCodeSplit?: boolean
preprocessOptions?: CssPreprocessOptions
modulesOptions?: SFCAsyncStyleCompileOptions['modulesOptions']
emitAssets: boolean
}

export const createBuildCssPlugin = ({
Expand All @@ -43,10 +44,10 @@ export const createBuildCssPlugin = ({
inlineLimit = 0,
cssCodeSplit = true,
preprocessOptions,
modulesOptions = {}
modulesOptions = {},
emitAssets
}: BuildCssOption): Plugin => {
const styles: Map<string, string> = new Map()
const assets = new Map<string, Buffer>()
let staticCss = ''

return {
Expand Down Expand Up @@ -97,22 +98,28 @@ export const createBuildCssPlugin = ({
const file = path.posix.isAbsolute(rawUrl)
? path.join(root, rawUrl)
: path.join(fileDir, rawUrl)
const { fileName, content, url } = await resolveAsset(
let { fileName, content, url } = await resolveAsset(
file,
root,
publicBase,
assetsDir,
inlineLimit
)
if (fileName && content) {
assets.set(fileName, content)
if (!url && emitAssets && fileName && content) {
url =
'import.meta.ROLLUP_FILE_URL_' +
this.emitFile({
name: fileName,
type: 'asset',
source: content
})
}
debug(
`url(${rawUrl}) -> ${
url.startsWith('data:') ? `base64 inlined` : `url(${url})`
url!.startsWith('data:') ? `base64 inlined` : `${file}`
}`
)
return url
return url!
})
}

Expand Down Expand Up @@ -142,6 +149,13 @@ export const createBuildCssPlugin = ({
}
}

let match
while ((match = injectAssetRe.exec(chunkCSS))) {
const outputFilepath =
publicBase + slash(path.join(assetsDir, this.getFileName(match[1])))
chunkCSS = chunkCSS.replace(match[0], outputFilepath)
}

if (cssCodeSplit) {
code = code.replace(cssInjectionRE, '')
// for each dynamic entry chunk, collect its css and inline it as JS
Expand All @@ -168,21 +182,19 @@ export const createBuildCssPlugin = ({

async generateBundle(_options, bundle) {
// minify css
if (minify) {
if (minify && staticCss) {
staticCss = minifyCSS(staticCss)
}

const cssFileName = `style.${hash_sum(staticCss)}.css`

bundle[cssFileName] = {
name: cssFileName,
isAsset: true,
type: 'asset',
fileName: cssFileName,
source: staticCss
if (emitAssets) {
if (staticCss) {
this.emitFile({
name: 'style.css',
type: 'asset',
source: staticCss
})
}
}

registerAssets(assets, bundle)
}
}
}
Expand Down
23 changes: 12 additions & 11 deletions src/node/build/buildPluginWasm.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Plugin } from 'rollup'
import { resolveAsset, registerAssets } from './buildPluginAsset'
import { resolveAsset } from './buildPluginAsset'

const wasmHelperId = 'vite/wasm-helper'

Expand Down Expand Up @@ -36,10 +36,9 @@ export const createBuildWasmPlugin = (
root: string,
publicBase: string,
assetsDir: string,
inlineLimit: number
inlineLimit: number,
emitAssets: boolean
): Plugin => {
const assets = new Map<string, Buffer>()

return {
name: 'vite:wasm',

Expand All @@ -55,26 +54,28 @@ export const createBuildWasmPlugin = (
}

if (id.endsWith('.wasm')) {
const { fileName, content, url } = await resolveAsset(
let { fileName, content, url } = await resolveAsset(
id,
root,
publicBase,
assetsDir,
inlineLimit
)
if (fileName && content) {
assets.set(fileName, content)
if (!url && emitAssets && fileName && content) {
url =
'import.meta.ROLLUP_FILE_URL_' +
this.emitFile({
name: fileName,
type: 'asset',
source: content
})
}

return `
import initWasm from "${wasmHelperId}"
export default opts => initWasm(opts, ${JSON.stringify(url)})
`
}
},

generateBundle(_options, bundle) {
registerAssets(assets, bundle)
}
}
}
54 changes: 24 additions & 30 deletions src/node/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ export async function build(options: BuildConfig): Promise<BuildResult> {
'process.env': JSON.stringify({ NODE_ENV: resolvedMode }),
'import.meta.hot': `false`
},
sourcemap
!!sourcemap
),
// vite:css
createBuildCssPlugin({
Expand All @@ -392,17 +392,25 @@ export async function build(options: BuildConfig): Promise<BuildResult> {
inlineLimit: assetsInlineLimit,
cssCodeSplit,
preprocessOptions: cssPreprocessOptions,
modulesOptions: cssModuleOptions
modulesOptions: cssModuleOptions,
emitAssets: write && emitAssets
}),
// vite:asset
createBuildAssetPlugin(
root,
resolver,
publicBasePath,
assetsDir,
assetsInlineLimit
assetsInlineLimit,
write && emitAssets
),
createBuildWasmPlugin(
root,
publicBasePath,
assetsDir,
assetsInlineLimit,
write && emitAssets
),
createBuildWasmPlugin(root, publicBasePath, assetsDir, assetsInlineLimit),
enableEsbuild
? createEsbuildRenderChunkPlugin(esbuildTarget, minify === 'esbuild')
: undefined,
Expand All @@ -419,12 +427,13 @@ export async function build(options: BuildConfig): Promise<BuildResult> {
].filter(Boolean)
})

const { output } = await bundle.generate({
const { output } = await bundle.write({
dir: resolvedAssetsPath,
format: 'es',
sourcemap,
entryFileNames: `[name].[hash].js`,
chunkFileNames: `[name].[hash].js`,
assetFileNames: `[name].[hash].[ext]`,
...rollupOutputOptions
})

Expand All @@ -433,14 +442,11 @@ export async function build(options: BuildConfig): Promise<BuildResult> {
const indexHtml = emitIndex ? renderIndex(output) : ''

if (write) {
const cwd = process.cwd()
const writeFile = async (
const printFilesInfo = async (
filepath: string,
content: string | Uint8Array,
type: WriteType
) => {
await fs.ensureDir(path.dirname(filepath))
await fs.writeFile(filepath, content)
if (!silent) {
const needCompression =
type === WriteType.JS ||
Expand All @@ -453,36 +459,26 @@ export async function build(options: BuildConfig): Promise<BuildResult> {
: ``
console.log(
`${chalk.gray(`[write]`)} ${writeColors[type](
path.relative(cwd, filepath)
path.relative(process.cwd(), filepath)
)} ${(content.length / 1024).toFixed(2)}kb${compressed}`
)
}
}

await fs.ensureDir(outDir)

// write js chunks and assets
// print chunk and assets info
for (const chunk of output) {
const filepath = path.join(resolvedAssetsPath, chunk.fileName)
if (chunk.type === 'chunk') {
// write chunk
const filepath = path.join(resolvedAssetsPath, chunk.fileName)
let code = chunk.code
if (chunk.map) {
code += `\n//# sourceMappingURL=${path.basename(filepath)}.map`
}
await writeFile(filepath, code, WriteType.JS)
await printFilesInfo(filepath, chunk.code, WriteType.JS)
if (chunk.map) {
await writeFile(
await printFilesInfo(
filepath + '.map',
chunk.map.toString(),
WriteType.SOURCE_MAP
)
}
} else if (emitAssets) {
if (!chunk.source) continue
// write asset
const filepath = path.join(resolvedAssetsPath, chunk.fileName)
await writeFile(
await printFilesInfo(
filepath,
chunk.source,
chunk.fileName.endsWith('.css') ? WriteType.CSS : WriteType.ASSET
Expand All @@ -492,11 +488,9 @@ export async function build(options: BuildConfig): Promise<BuildResult> {

// write html
if (indexHtml && emitIndex) {
await writeFile(
path.join(outDir, 'index.html'),
indexHtml,
WriteType.HTML
)
const outputHtmlPath = path.join(outDir, 'index.html')
await fs.writeFile(outputHtmlPath, indexHtml)
await printFilesInfo(outputHtmlPath, indexHtml, WriteType.HTML)
}

// copy over /public if it exists
Expand Down
Loading

0 comments on commit 21b4aa7

Please sign in to comment.