Skip to content

Commit

Permalink
feat(vite): emitAssetsWithModule in library mode
Browse files Browse the repository at this point in the history
  • Loading branch information
yoyo930021 committed Feb 10, 2023
1 parent d953536 commit e176e0a
Show file tree
Hide file tree
Showing 11 changed files with 121 additions and 6 deletions.
27 changes: 26 additions & 1 deletion docs/config/build-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,35 @@ Options to pass on to [@rollup/plugin-dynamic-import-vars](https://github.com/ro
## build.lib
- **Type:** `{ entry: string | string[] | { [entryAlias: string]: string }, name?: string, formats?: ('es' | 'cjs' | 'umd' | 'iife')[], fileName?: string | ((format: ModuleFormat, entryName: string) => string) }`
- **Type:** `{ entry: string | string[] | { [entryAlias: string]: string }, name?: string, formats?: ('es' | 'cjs' | 'umd' | 'iife')[], fileName?: string | ((format: ModuleFormat, entryName: string) => string), emitAssetsWithModule?: boolean }`
- **Related:** [Library Mode](/guide/build#library-mode)
Build as a library. `entry` is required since the library cannot use HTML as entry. `name` is the exposed global variable and is required when `formats` includes `'umd'` or `'iife'`. Default `formats` are `['es', 'umd']`, or `['es', 'cjs']`, if multiple entries are used. `fileName` is the name of the package file output, default `fileName` is the name option of package.json, it can also be defined as function taking the `format` and `entryAlias` as arguments.
Build as a library. `entry` is required since the library cannot use HTML as entry. `name` is the exposed global variable and is required when `formats` includes `'umd'` or `'iife'`. Default `formats` are `['es', 'umd']`. `fileName` is the name of the package file output, default `fileName` is the name option of package.json, it can also be defined as function taking the `format` as an argument.
The `emitAssetsWithModule` will try to generate module with assets. When enable this option, please use bundler in user project.
### Generate example
```javascript
// Code
import image from '@/assets/image/banner.jpg'
// Generate
import img from './assets/banner.87342es.jpg'
// Use bundler to resolve it. Ex: Vite, Webpack (url-loader)
```

```css
/* Code */
body {
background-image: url('../assets/image/banner.jpg');
}
/* Generate */
body {
background-image: url('./assets/banner.87342es.jpg');
}
/* Use bundler to resolve it. Ex: Vite, Webpack (css-loader) */
```

## build.manifest

Expand Down
17 changes: 13 additions & 4 deletions packages/vite/src/node/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,13 @@ export interface LibraryOptions {
* format as an argument.
*/
fileName?: string | ((format: ModuleFormat, entryName: string) => string)
/**
* We replace asset to base64 for lib mode by default, It can handle most of the contexts.
* But it will add a lot of size in JS/CSS. When you enable this option, vite will emit asset to file and generate original import code in JS.
* When user use this lib with bundler, it can get correct image url.
* @default false
*/
emitAssetsWithModule?: boolean
}

export type LibraryFormats = 'es' | 'cjs' | 'umd' | 'iife'
Expand Down Expand Up @@ -1056,7 +1063,8 @@ export function toOutputFilePathInJS(
) => string | { runtime: string },
): string | { runtime: string } {
const { renderBuiltUrl } = config.experimental
let relative = config.base === '' || config.base === './'
const base = config.build.lib && config.build.lib.emitAssetsWithModule ? './' : config.base
let relative = base === '' || base === './'
if (renderBuiltUrl) {
const result = renderBuiltUrl(filename, {
hostId,
Expand All @@ -1078,7 +1086,7 @@ export function toOutputFilePathInJS(
if (relative && !config.build.ssr) {
return toRelative(filename, hostId)
}
return joinUrlSegments(config.base, filename)
return joinUrlSegments(base, filename)
}

export function createToImportMetaURLBasedRelativeRuntime(
Expand All @@ -1101,7 +1109,8 @@ export function toOutputFilePathWithoutRuntime(
toRelative: (filename: string, hostId: string) => string,
): string {
const { renderBuiltUrl } = config.experimental
let relative = config.base === '' || config.base === './'
const base = config.build.lib && config.build.lib.emitAssetsWithModule ? './' : config.base
let relative = base === '' || base === './'
if (renderBuiltUrl) {
const result = renderBuiltUrl(filename, {
hostId,
Expand All @@ -1125,7 +1134,7 @@ export function toOutputFilePathWithoutRuntime(
if (relative && !config.build.ssr) {
return toRelative(filename, hostId)
} else {
return joinUrlSegments(config.base, filename)
return joinUrlSegments(base, filename)
}
}

Expand Down
12 changes: 11 additions & 1 deletion packages/vite/src/node/plugins/asset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { cleanUrl, getHash, joinUrlSegments, normalizePath } from '../utils'
import { FS_PREFIX } from '../constants'

export const assetUrlRE = /__VITE_ASSET__([a-z\d]+)__(?:\$_(.*?)__)?/g
const assetUrlRENoGFlag = /__VITE_ASSET__([a-z\d]+)__(?:\$_(.*?)__)?/

const rawRE = /(?:\?|&)raw(?:&|$)/
const urlRE = /(\?|&)url(?:&|$)/
Expand Down Expand Up @@ -127,6 +128,9 @@ export function renderAssetUrlInJS(
export function assetPlugin(config: ResolvedConfig): Plugin {
registerCustomMime()

const isEmitAssetsWithModule =
config.build.lib && config.build.lib.emitAssetsWithModule

return {
name: 'vite:asset',

Expand All @@ -136,6 +140,9 @@ export function assetPlugin(config: ResolvedConfig): Plugin {
},

resolveId(id) {
if (assetUrlRENoGFlag.test(id)) {
return { id, external: 'absolute' }
}
if (!config.assetsInclude(cleanUrl(id))) {
return
}
Expand Down Expand Up @@ -169,6 +176,9 @@ export function assetPlugin(config: ResolvedConfig): Plugin {

id = id.replace(urlRE, '$1').replace(/[?&]$/, '')
const url = await fileToUrl(id, config, this)
if (isEmitAssetsWithModule) {
return `import img from ${JSON.stringify(url)};export default img;`
}
return `export default ${JSON.stringify(url)}`
},

Expand Down Expand Up @@ -323,7 +333,7 @@ async function fileToBuiltUrl(

let url: string
if (
config.build.lib ||
(config.build.lib && !config.build.lib.emitAssetsWithModule) ||
(!file.endsWith('.svg') &&
!file.endsWith('.html') &&
content.length < Number(config.build.assetsInlineLimit) &&
Expand Down
5 changes: 5 additions & 0 deletions packages/vite/src/node/plugins/css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,8 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin {
// since output formats have no effect on the generated CSS.
let outputToExtractedCSSMap: Map<NormalizedOutputOptions, string>
let hasEmitted = false
const isEmitAssetsWithModule =
config.build.lib && config.build.lib.emitAssetsWithModule

const rollupOptionsOutput = config.build.rollupOptions.output
const assetFileNames = (
Expand Down Expand Up @@ -497,6 +499,9 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin {

const toRelative = (filename: string, importer: string) => {
// relative base + extracted CSS
if (!config.build.cssCodeSplit) {
return filename.startsWith('.') ? filename : './' + filename
}
const relativePath = path.posix.relative(cssAssetDirname!, filename)
return relativePath.startsWith('.')
? relativePath
Expand Down
13 changes: 13 additions & 0 deletions playground/lib/__tests__/lib.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { describe, expect, test } from 'vitest'
import {
getBg,
isBuild,
isServe,
page,
Expand Down Expand Up @@ -35,6 +36,18 @@ describe.runIf(isBuild)('build', () => {
)
})

test('lib: emitAssetsWithModule:undefined|false = is inlined', async () => {
const match = `data:image/png;base64`
expect(await getBg('.emitAssetsWithModule-default')).toMatch(match)
})

test('lib: emitAssetsWithModule:true = is emitted', async () => {
const code = readFile('dist/lib2/emit-assets-with-module.js')
expect(code).toMatch(/^import img from "\.\/assets\/asset\..*\.png";/)
const cssCode = readFile('dist/lib2/style.css')
expect(cssCode).toMatch(/url\('\.\/assets\/asset\..*\.png'\)/)
})

test('Library mode does not include `preload`', async () => {
await untilUpdated(
() => page.textContent('.dynamic-import-message'),
Expand Down
9 changes: 9 additions & 0 deletions playground/lib/__tests__/serve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,15 @@ export async function serve(): Promise<{ close(): Promise<void> }> {
configFile: path.resolve(__dirname, '../vite.nominify.config.js'),
})

await build({
root: rootDir,
logLevel: 'silent',
configFile: path.resolve(
__dirname,
'../vite.emitAssetsWithModule.config.js'
)
})

// start static file server
const serve = sirv(path.resolve(rootDir, 'dist'))
const httpServer = http.createServer((req, res) => {
Expand Down
11 changes: 11 additions & 0 deletions playground/lib/index.dist.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@
<div class="iife"></div>
<div class="dynamic-import-message"></div>

<div
class="emitAssetsWithModule-default"
style="
background-size: contain;
background-repeat: no-repeat;
padding-left: 20px;
"
>
<-background-image should be inline unless emitAssets:true is set
</div>

<script>
// shim test preserve process.env.NODE_ENV
globalThis.process = {
Expand Down
3 changes: 3 additions & 0 deletions playground/lib/src/emitAssetsWithModule.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
body {
background-image: url('../../assets/nested/asset.png');
}
4 changes: 4 additions & 0 deletions playground/lib/src/emitAssetsWithModule.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import asset from '../../assets/nested/asset.png'
import './emitAssetsWithModule.css'

console.log(asset)
7 changes: 7 additions & 0 deletions playground/lib/src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,11 @@ export default function myLib(sel) {

// Env vars should not be replaced
console.log(process.env.NODE_ENV)

document.querySelector(
'.emitAssetsWithModule-default'
).style.backgroundImage = `url(${new URL(
`../../assets/nested/asset.png`,
import.meta.url
)})`
}
19 changes: 19 additions & 0 deletions playground/lib/vite.emitAssetsWithModule.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const fs = require('fs')
const path = require('path')

/**
* @type {import('vite').UserConfig}
*/
module.exports = {
build: {
minify: false,
lib: {
entry: path.resolve(__dirname, 'src/emitAssetsWithModule.js'),
formats: ['es'],
name: 'emitAssetsWithModule',
fileName: () => `emit-assets-with-module.js`,
emitAssetsWithModule: true
},
outDir: 'dist/lib2'
}
}

0 comments on commit e176e0a

Please sign in to comment.