Skip to content

Commit e5e7be7

Browse files
authored
Merge pull request #1 from vite-plugin/v0.1.0
v0.1.0
2 parents eff219f + 0942228 commit e5e7be7

14 files changed

Lines changed: 2848 additions & 1 deletion

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,3 +128,7 @@ dist
128128
.yarn/build-state.yml
129129
.yarn/install-state.gz
130130
.pnp.*
131+
132+
package-lock.json
133+
pnpm-lock.yaml
134+
# yarn.lock

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## 0.1.0 (2023-06-30)
2+
3+
- First version is out! 🚀

README.md

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,67 @@
11
# vite-plugin-webpack-prebundle
2-
A webpack-based pre-bundle solution, mainly used to support Node.js and Electron.
2+
3+
A Webpack-based pre-bundle solution, mainly used for adapting Node.js and Electron.
4+
5+
[![NPM version](https://img.shields.io/npm/v/vite-plugin-webpack-prebundle.svg)](https://npmjs.org/package/vite-plugin-webpack-prebundle)
6+
[![NPM Downloads](https://img.shields.io/npm/dm/vite-plugin-webpack-prebundle.svg)](https://npmjs.org/package/vite-plugin-webpack-prebundle)
7+
8+
English | [简体中文](./README.zh-CN.md)
9+
10+
## Install
11+
12+
```bash
13+
npm i -D vite-plugin-webpack-prebundle
14+
```
15+
16+
## Usage
17+
18+
```js
19+
import prebundle from 'vite-plugin-webpack-prebundle'
20+
21+
export default {
22+
plugins: [
23+
prebundle({
24+
modules: [
25+
'foo',
26+
'bar',
27+
],
28+
})
29+
]
30+
}
31+
```
32+
33+
## API
34+
35+
```ts
36+
export interface PrebundleOptions {
37+
/** An array of module names that need to be pre-bundle. */
38+
modules: string[]
39+
config?: (config: Configuration) => Configuration | undefined | Promise<Configuration | undefined>
40+
}
41+
```
42+
43+
## Why
44+
45+
This is a Pre-Bundle solution designed for Node/Electron Apps, which is consistent with Vite's built-in [Dependency Pre-Bundling](https://vitejs.dev/guide/dep-pre-bundling.html#dependency-pre-bundling) behavior. The reason for using Webpack is that it's currently the most compatible bundler for Node/Electron Apps.
46+
47+
## Compare
48+
49+
<table>
50+
<thead>
51+
<th>Pre-Bundle solution</th>
52+
<th>Web</th>
53+
<th>Node/Electron</th>
54+
</thead>
55+
<tbody>
56+
<tr>
57+
<td>Vite's built-in Dependency Pre-Bundling</td>
58+
<td>✅</td>
59+
<td>❌</td>
60+
</tr>
61+
<tr>
62+
<td>vite-plugin-webpack-prebundle</td>
63+
<td>✅</td>
64+
<td>✅</td>
65+
</tr>
66+
</tbody>
67+
</table>

README.zh-CN.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# vite-plugin-webpack-prebundle
2+
3+
一个基于 Webpack 的预构建(pre-bundle) 方案, 主要用于适配 Node.js 和 Electron。
4+
5+
[![NPM version](https://img.shields.io/npm/v/vite-plugin-webpack-prebundle.svg)](https://npmjs.org/package/vite-plugin-webpack-prebundle)
6+
[![NPM Downloads](https://img.shields.io/npm/dm/vite-plugin-webpack-prebundle.svg)](https://npmjs.org/package/vite-plugin-webpack-prebundle)
7+
8+
[English](./README.md) | 简体中文
9+
10+
## Install
11+
12+
```bash
13+
npm i -D vite-plugin-webpack-prebundle
14+
```
15+
16+
## Usage
17+
18+
```js
19+
import prebundle from 'vite-plugin-webpack-prebundle'
20+
21+
export default {
22+
plugins: [
23+
prebundle({
24+
modules: [
25+
'foo',
26+
'bar',
27+
],
28+
})
29+
]
30+
}
31+
```
32+
33+
## API
34+
35+
```ts
36+
export interface PrebundleOptions {
37+
/** An array of module names that need to be pre-bundle. */
38+
modules: string[]
39+
config?: (config: Configuration) => Configuration | undefined | Promise<Configuration | undefined>
40+
}
41+
```
42+
43+
## Why
44+
45+
这是一个专为 Node/Electron 应用而生的 Pre-Bundling 方案,与 Vite 内置的 [Dependency Pre-Bundling](https://vitejs.dev/guide/dep-pre-bundling.html#dependency-pre-bundling) 行为一致。使用 Webpack 的原因是对于 Node/Electron 应用来说,它是目前兼容性最好的 Bundler。
46+
47+
## 对比
48+
49+
<table>
50+
<thead>
51+
<th>Pre-Bundle solution</th>
52+
<th>Web</th>
53+
<th>Node/Electron</th>
54+
</thead>
55+
<tbody>
56+
<tr>
57+
<td>Vite 内置的 Dependency Pre-Bundling</td>
58+
<td>✅</td>
59+
<td>❌</td>
60+
</tr>
61+
<tr>
62+
<td>vite-plugin-webpack-prebundle</td>
63+
<td>✅</td>
64+
<td>✅</td>
65+
</tr>
66+
</tbody>
67+
</table>

package.json

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
{
2+
"name": "vite-plugin-webpack-prebundle",
3+
"version": "0.1.0",
4+
"description": "A Webpack-based pre-bundle solution, mainly used for adapting Node.js and Electron",
5+
"main": "./dist/index.js",
6+
"types": "./dist/index.d.ts",
7+
"exports": {
8+
".": {
9+
"types": "./dist/index.d.ts",
10+
"import": "./dist/index.mjs",
11+
"require": "./dist/index.js"
12+
},
13+
"./*": "./*"
14+
},
15+
"repository": "https://github.com/vite-plugin/vite-plugin-webpack-prebundle.git",
16+
"author": "野鸡没名 <asd308487730@gmail.com>",
17+
"license": "MIT",
18+
"scripts": {
19+
"dev": "vite build --watch",
20+
"build": "vite build",
21+
"types": "tsc --emitDeclarationOnly",
22+
"prepublishOnly": "npm run build && npm run test",
23+
"build:test": "vite build -c test/fixtures/vite.config.ts",
24+
"test": "vitest run"
25+
},
26+
"dependencies": {
27+
"lib-esm": "~0.4.2",
28+
"webpack": "^5.70.0"
29+
},
30+
"devDependencies": {
31+
"@vercel/webpack-asset-relocator-loader": "1.7.3",
32+
"file-type": "^19.0.0",
33+
"node-loader": "^2.0.0",
34+
"sqlite3": "^5.1.7",
35+
"typescript": "^5.5.2",
36+
"vite": "^5.3.2",
37+
"vite-plugin-utils": "^0.4.3",
38+
"vitest": "^1.6.0"
39+
},
40+
"files": [
41+
"dist"
42+
],
43+
"keywords": [
44+
"vite",
45+
"plugin",
46+
"webpack",
47+
"prebundle",
48+
"electron",
49+
"node"
50+
]
51+
}

src/index.ts

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
import fs from 'node:fs'
2+
import path from 'node:path'
3+
import {
4+
type Plugin,
5+
type UserConfig,
6+
normalizePath,
7+
} from 'vite'
8+
import type { Configuration } from 'webpack'
9+
import { COLOURS } from 'vite-plugin-utils/function'
10+
import {
11+
createCjs,
12+
cjs2esm,
13+
} from './utils'
14+
15+
export interface BundledRecord {
16+
name: string
17+
code: string
18+
filename: string
19+
}
20+
21+
export interface PrebundleOptions {
22+
/** An array of module names that need to be pre-bundle. */
23+
modules: string[]
24+
config?: (config: Configuration) => Configuration | undefined | Promise<Configuration | undefined>
25+
}
26+
27+
const cjs = createCjs(import.meta.url)
28+
const TAG = '[vite-plugin-webpack-prebundle]'
29+
// `nativesMap` is placed in the global scope and can be effective for multiple builds.
30+
const bundledMap = new Map<string, BundledRecord>
31+
const IDPrefix = '\0webpack-prebundle'
32+
let output: string
33+
34+
export default function native(options: PrebundleOptions): Plugin {
35+
return {
36+
name: 'vite-plugin-webpack-prebundle',
37+
enforce: 'pre',
38+
configResolved(config) {
39+
// Use the `build.outDir` for those C/C++ addons assets.
40+
output = normalizePath(path.join(config.root, config.build.outDir))
41+
},
42+
async resolveId(source) {
43+
if (options.modules.includes(source)) {
44+
const bundled = bundledMap.get(source)
45+
if (!bundled) {
46+
try {
47+
await webpackBundle(source, output, options.config)
48+
const filename = path.posix.join(output, source + '.js')
49+
const code = cjs2esm(
50+
// After bundle, it must be a cjs module.
51+
filename,
52+
fs.readFileSync(filename, 'utf8'),
53+
)
54+
55+
fs.writeFileSync(filename, code)
56+
bundledMap.set(source, {
57+
name: source,
58+
code,
59+
filename,
60+
})
61+
} catch (error: any) {
62+
console.error(`\n${TAG}`, error)
63+
process.exit(1)
64+
}
65+
}
66+
67+
return IDPrefix + source
68+
}
69+
},
70+
load(id) {
71+
if (id.startsWith(IDPrefix)) {
72+
const name = id.replace(IDPrefix, '')
73+
const bundled = bundledMap.get(name)
74+
if (bundled) {
75+
return bundled.code
76+
}
77+
}
78+
},
79+
closeBundle() {
80+
if (!(process.env.DEBUG || process.env.NODE_ENV === 'test')) {
81+
for (const [, bundled] of bundledMap) {
82+
// Remove unused bundle files
83+
fs.unlinkSync(bundled.filename)
84+
fs.unlinkSync(bundled.filename + '.map')
85+
}
86+
}
87+
},
88+
async config(config) {
89+
modifyCommonjs(config, options.modules)
90+
// Run build are not necessary.
91+
modifyOptimizeDeps(config, options.modules)
92+
},
93+
}
94+
}
95+
96+
function modifyCommonjs(config: UserConfig, modules: string[]) {
97+
config.build ??= {}
98+
config.build.commonjsOptions ??= {}
99+
if (config.build.commonjsOptions.ignore) {
100+
if (typeof config.build.commonjsOptions.ignore === 'function') {
101+
const userIgnore = config.build.commonjsOptions.ignore
102+
config.build.commonjsOptions.ignore = id => {
103+
if (userIgnore?.(id) === true) {
104+
return true
105+
}
106+
return modules.includes(id)
107+
}
108+
} else {
109+
// @ts-ignore
110+
config.build.commonjsOptions.ignore.push(...modules)
111+
}
112+
} else {
113+
config.build.commonjsOptions.ignore = modules
114+
}
115+
}
116+
117+
function modifyOptimizeDeps(config: UserConfig, exclude: string[]) {
118+
config.optimizeDeps ??= {}
119+
config.optimizeDeps.exclude ??= []
120+
for (const str of exclude) {
121+
if (!config.optimizeDeps.exclude.includes(str)) {
122+
// Avoid Vite secondary pre-bundle
123+
config.optimizeDeps.exclude.push(str)
124+
}
125+
}
126+
}
127+
128+
async function webpackBundle(
129+
name: string,
130+
output: string,
131+
webpackConfig: PrebundleOptions['config']
132+
) {
133+
const { validate, webpack } = cjs.require('webpack') as typeof import('webpack')
134+
135+
return new Promise<null>(async (resolve, reject) => {
136+
let options: Configuration = {
137+
mode: 'none',
138+
target: 'node14',
139+
entry: { [name]: name },
140+
output: {
141+
library: {
142+
type: 'commonjs2',
143+
},
144+
path: output,
145+
filename: '[name].js',
146+
},
147+
module: {
148+
rules: [
149+
// TODO: Add some commonly used loaders.
150+
],
151+
},
152+
devtool: 'source-map',
153+
}
154+
155+
if (webpackConfig) {
156+
options = await webpackConfig(options) ?? options
157+
}
158+
159+
try {
160+
validate(options)
161+
} catch (error: any) {
162+
reject(COLOURS.red(error.message))
163+
return
164+
}
165+
166+
webpack(options).run((error, stats) => {
167+
if (error) {
168+
reject(error)
169+
return
170+
}
171+
172+
if (stats?.hasErrors()) {
173+
const errorMsg = stats.toJson().errors?.map(msg => msg.message).join('\n')
174+
175+
if (errorMsg) {
176+
reject(COLOURS.red(errorMsg))
177+
return
178+
}
179+
}
180+
181+
console.log(`${TAG}`, name, COLOURS.green('build success'))
182+
resolve(null)
183+
})
184+
})
185+
}

0 commit comments

Comments
 (0)