Skip to content

Commit faadadf

Browse files
committed
feat: build --target lib/wc
1 parent 120d5c5 commit faadadf

File tree

11 files changed

+361
-218
lines changed

11 files changed

+361
-218
lines changed

packages/@vue/cli-service/lib/Service.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,8 @@ module.exports = class Service {
9797
'./config/base',
9898
'./config/css',
9999
'./config/dev',
100-
'./config/prod'
100+
'./config/prod',
101+
'./config/app'
101102
].map(idToPlugin)
102103

103104
if (inlinePlugins) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import Component from '~entry'
2+
3+
export default Component
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// TODO
2+
3+
import Vue from 'vue'
4+
import Component from '~entry'
5+
6+
new Vue(Component)

packages/@vue/cli-service/lib/commands/build.js renamed to packages/@vue/cli-service/lib/commands/build/index.js

+29-11
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,60 @@
11
const defaults = {
22
mode: 'production',
3-
target: 'app'
3+
target: 'app',
4+
entry: 'src/App.vue'
45
}
56

67
module.exports = (api, options) => {
78
api.registerCommand('build', {
89
description: 'build for production',
910
usage: 'vue-cli-service build [options]',
1011
options: {
11-
'--mode': `specify env mode (default: ${defaults.mode})`
12-
// TODO build target
13-
// '--target': `app | lib | web-component (default: ${defaults.target})`,
14-
// '--format': `How the lib is exposed (esm, umd, cjs, amd). Default: esm`
15-
// '--name': `Library name for umd/iife export`
12+
'--mode': `specify env mode (default: ${defaults.mode})`,
13+
'--target': `app | lib | web-component (default: ${defaults.target})`,
14+
'--entry': `entry for lib or web-component (default: ${defaults.entry})`,
15+
'--name': `name for lib or web-component (default: "name" in package.json)`
1616
}
1717
}, args => {
18-
api.setMode(args.mode || defaults.mode)
18+
args = Object.assign({}, defaults, args)
19+
api.setMode(args.mode)
1920

2021
const chalk = require('chalk')
2122
const rimraf = require('rimraf')
2223
const webpack = require('webpack')
2324
const {
25+
log,
2426
done,
2527
info,
2628
logWithSpinner,
2729
stopSpinner
2830
} = require('@vue/cli-shared-utils')
2931

30-
console.log()
31-
logWithSpinner(`Building for production...`)
32+
log()
33+
if (args.target === 'app') {
34+
logWithSpinner(`Building for production...`)
35+
} else {
36+
// setting this disables app-only configs
37+
process.env.VUE_CLI_TARGET = args.target
38+
// when building as a lib, inline all static asset files
39+
// since there is no publicPath handling
40+
process.env.VUE_CLI_INLINE_LIMIT = Infinity
41+
logWithSpinner(`Building for production as ${args.target}...`)
42+
}
3243

3344
return new Promise((resolve, reject) => {
3445
const targetDir = api.resolve(options.outputDir)
3546
rimraf(targetDir, err => {
3647
if (err) {
3748
return reject(err)
3849
}
39-
const webpackConfig = api.resolveWebpackConfig()
50+
let webpackConfig
51+
if (args.target === 'lib') {
52+
webpackConfig = require('./resolveLibConfig')(api, args)
53+
} else if (args.target === 'web-component') {
54+
webpackConfig = require('./resolveWebComponentConfig')(api, args)
55+
} else {
56+
webpackConfig = api.resolveWebpackConfig()
57+
}
4058
webpack(webpackConfig, (err, stats) => {
4159
stopSpinner(false)
4260
if (err) {
@@ -57,7 +75,7 @@ module.exports = (api, options) => {
5775
return reject(`Build failed with errors.`)
5876
}
5977

60-
if (!args.silent) {
78+
if (!args.silent && args.target === 'app') {
6179
done(`Build complete. The ${chalk.cyan(options.outputDir)} directory is ready to be deployed.\n`)
6280
if (options.baseUrl === '/') {
6381
info(`The app is built assuming that it will be deployed at the root of a domain.`)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
module.exports = (api, { entry, name }) => {
2+
const genConfig = (format, postfix = format) => {
3+
api.chainWebpack(config => {
4+
const libName = name || api.service.pkg.name
5+
6+
config.entryPoints.clear()
7+
// set proxy entry for *.vue files
8+
if (/\.vue$/.test(entry)) {
9+
config
10+
.entry(`${libName}.${postfix}`)
11+
.add(require.resolve('./entry-lib.js'))
12+
config.resolve
13+
.alias
14+
.set('~entry', api.resolve(entry))
15+
} else {
16+
config
17+
.entry(`${libName}.${postfix}`)
18+
.add(api.resolve(entry))
19+
}
20+
21+
config.output
22+
.filename(`[name].js`)
23+
.library(libName)
24+
.libraryExport('default')
25+
.libraryTarget(format)
26+
27+
// adjust css output name
28+
config
29+
.plugin('extract-css')
30+
.tap(args => {
31+
args[0].filename = `${libName}.css`
32+
return args
33+
})
34+
35+
// only minify min entry
36+
config
37+
.plugin('uglify')
38+
.tap(args => {
39+
args[0].include = /\.min\.js$/
40+
return args
41+
})
42+
43+
// externalize Vue in case user imports it
44+
config
45+
.externals({
46+
vue: {
47+
commonjs: 'vue',
48+
commonjs2: 'vue',
49+
root: 'Vue'
50+
}
51+
})
52+
})
53+
54+
return api.resolveWebpackConfig()
55+
}
56+
57+
return [
58+
genConfig('commonjs2', 'common'),
59+
genConfig('umd'),
60+
genConfig('umd', 'umd.min')
61+
]
62+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
module.exports = (api, { entry, name }) => {
2+
const genConfig = postfix => {
3+
api.chainWebpack(config => {
4+
const libName = name || api.service.pkg.name
5+
6+
config.entryPoints.clear()
7+
// set proxy entry for *.vue files
8+
if (/\.vue$/.test(entry)) {
9+
config
10+
.entry(`${libName}.${postfix}`)
11+
.add(require.resolve('./entry-web-component.js'))
12+
config.resolve
13+
.alias
14+
.set('~entry', api.resolve(entry))
15+
} else {
16+
config
17+
.entry(`${libName}.${postfix}`)
18+
.add(api.resolve(entry))
19+
}
20+
21+
config.output
22+
.filename(`[name].js`)
23+
24+
// only minify min entry
25+
config
26+
.plugin('uglify')
27+
.tap(args => {
28+
args[0].include = /\.min\.js$/
29+
return args
30+
})
31+
32+
// externalize Vue in case user imports it
33+
config
34+
.externals({
35+
vue: 'Vue'
36+
})
37+
38+
// TODO handle CSS (insert in shadow DOM)
39+
})
40+
41+
return api.resolveWebpackConfig()
42+
}
43+
44+
return [
45+
genConfig('web-component'),
46+
genConfig('web-component.min')
47+
]
48+
}
+156
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
// config that are specific to --target app
2+
3+
module.exports = (api, options) => {
4+
api.chainWebpack(webpackConfig => {
5+
// only apply when there's no alternative target
6+
if (process.env.VUE_CLI_TARGET) {
7+
return
8+
}
9+
10+
// inject preload/prefetch to HTML
11+
const PreloadPlugin = require('../webpack/PreloadPlugin')
12+
webpackConfig
13+
.plugin('preload')
14+
.use(PreloadPlugin, [{
15+
rel: 'preload',
16+
include: 'initial',
17+
fileBlacklist: [/\.map$/, /hot-update\.js$/]
18+
}])
19+
20+
webpackConfig
21+
.plugin('prefetch')
22+
.use(PreloadPlugin, [{
23+
rel: 'prefetch',
24+
include: 'asyncChunks'
25+
}])
26+
27+
// HTML plugin
28+
const fs = require('fs')
29+
const htmlPath = api.resolve('public/index.html')
30+
const resolveClientEnv = require('../util/resolveClientEnv')
31+
webpackConfig
32+
.plugin('html')
33+
.use(require('html-webpack-plugin'), [
34+
Object.assign(
35+
fs.existsSync(htmlPath) ? { template: htmlPath } : {},
36+
// expose client env to html template
37+
{ env: resolveClientEnv(options.baseUrl, true /* raw */) }
38+
)
39+
])
40+
41+
if (process.env.NODE_ENV === 'production') {
42+
// minify HTML
43+
webpackConfig
44+
.plugin('html')
45+
.tap(([options]) => [Object.assign(options, {
46+
minify: {
47+
removeComments: true,
48+
collapseWhitespace: true,
49+
removeAttributeQuotes: true
50+
// more options:
51+
// https://github.com/kangax/html-minifier#options-quick-reference
52+
},
53+
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
54+
chunksSortMode: 'dependency'
55+
})])
56+
57+
// Code splitting configs for better long-term caching
58+
// This needs to be updated when upgrading to webpack 4
59+
const CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin')
60+
61+
if (!options.dll) {
62+
// extract vendor libs into its own chunk for better caching, since they
63+
// are more likely to stay the same.
64+
webpackConfig
65+
.plugin('split-vendor')
66+
.use(CommonsChunkPlugin, [{
67+
name: 'vendor',
68+
minChunks (module) {
69+
// any required modules inside node_modules are extracted to vendor
70+
return (
71+
module.resource &&
72+
/\.js$/.test(module.resource) &&
73+
module.resource.indexOf(`node_modules`) > -1
74+
)
75+
}
76+
}])
77+
78+
// extract webpack runtime and module manifest to its own file in order to
79+
// prevent vendor hash from being updated whenever app bundle is updated
80+
webpackConfig
81+
.plugin('split-manifest')
82+
.use(CommonsChunkPlugin, [{
83+
name: 'manifest',
84+
minChunks: Infinity
85+
}])
86+
87+
// inline the manifest chunk into HTML
88+
webpackConfig
89+
.plugin('inline-manifest')
90+
.use(require('../webpack/InlineSourcePlugin'), [{
91+
include: /manifest\..*\.js$/
92+
}])
93+
94+
// since manifest is inlined, don't preload it anymore
95+
webpackConfig
96+
.plugin('preload')
97+
.tap(([options]) => {
98+
options.fileBlacklist.push(/manifest\..*\.js$/)
99+
return [options]
100+
})
101+
}
102+
103+
// This CommonsChunkPlugin instance extracts shared chunks from async
104+
// chunks and bundles them in a separate chunk, similar to the vendor chunk
105+
// see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk
106+
webpackConfig
107+
.plugin('split-vendor-async')
108+
.use(CommonsChunkPlugin, [{
109+
name: 'app',
110+
async: 'vendor-async',
111+
children: true,
112+
minChunks: 3
113+
}])
114+
115+
// DLL
116+
if (options.dll) {
117+
const webpack = require('webpack')
118+
const UglifyPlugin = require('uglifyjs-webpack-plugin')
119+
const getUglifyOptions = require('./uglifyOptions')
120+
const dllEntries = Array.isArray(options.dll)
121+
? options.dll
122+
: Object.keys(api.service.pkg.dependencies)
123+
124+
webpackConfig
125+
.plugin('dll')
126+
.use(require('autodll-webpack-plugin'), [{
127+
inject: true,
128+
inherit: true,
129+
path: 'js/',
130+
context: api.resolve('.'),
131+
filename: '[name].[hash:8].js',
132+
entry: {
133+
'vendor': [
134+
...dllEntries,
135+
'vue-loader/lib/component-normalizer'
136+
]
137+
},
138+
plugins: [
139+
new webpack.DefinePlugin(resolveClientEnv(options.baseUrl)),
140+
new UglifyPlugin(getUglifyOptions(options))
141+
]
142+
}])
143+
.after('preload')
144+
}
145+
146+
// copy static assets in public/
147+
webpackConfig
148+
.plugin('copy')
149+
.use(require('copy-webpack-plugin'), [[{
150+
from: api.resolve('public'),
151+
to: api.resolve(options.outputDir),
152+
ignore: ['index.html', '.*']
153+
}]])
154+
}
155+
})
156+
}

0 commit comments

Comments
 (0)