Skip to content

Commit

Permalink
feat: build --target lib/wc
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Jan 30, 2018
1 parent 120d5c5 commit faadadf
Show file tree
Hide file tree
Showing 11 changed files with 361 additions and 218 deletions.
3 changes: 2 additions & 1 deletion packages/@vue/cli-service/lib/Service.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ module.exports = class Service {
'./config/base',
'./config/css',
'./config/dev',
'./config/prod'
'./config/prod',
'./config/app'
].map(idToPlugin)

if (inlinePlugins) {
Expand Down
3 changes: 3 additions & 0 deletions packages/@vue/cli-service/lib/commands/build/entry-lib.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import Component from '~entry'

export default Component
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// TODO

import Vue from 'vue'
import Component from '~entry'

new Vue(Component)
Original file line number Diff line number Diff line change
@@ -1,42 +1,60 @@
const defaults = {
mode: 'production',
target: 'app'
target: 'app',
entry: 'src/App.vue'
}

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

const chalk = require('chalk')
const rimraf = require('rimraf')
const webpack = require('webpack')
const {
log,
done,
info,
logWithSpinner,
stopSpinner
} = require('@vue/cli-shared-utils')

console.log()
logWithSpinner(`Building for production...`)
log()
if (args.target === 'app') {
logWithSpinner(`Building for production...`)
} else {
// setting this disables app-only configs
process.env.VUE_CLI_TARGET = args.target
// when building as a lib, inline all static asset files
// since there is no publicPath handling
process.env.VUE_CLI_INLINE_LIMIT = Infinity
logWithSpinner(`Building for production as ${args.target}...`)
}

return new Promise((resolve, reject) => {
const targetDir = api.resolve(options.outputDir)
rimraf(targetDir, err => {
if (err) {
return reject(err)
}
const webpackConfig = api.resolveWebpackConfig()
let webpackConfig
if (args.target === 'lib') {
webpackConfig = require('./resolveLibConfig')(api, args)
} else if (args.target === 'web-component') {
webpackConfig = require('./resolveWebComponentConfig')(api, args)
} else {
webpackConfig = api.resolveWebpackConfig()
}
webpack(webpackConfig, (err, stats) => {
stopSpinner(false)
if (err) {
Expand All @@ -57,7 +75,7 @@ module.exports = (api, options) => {
return reject(`Build failed with errors.`)
}

if (!args.silent) {
if (!args.silent && args.target === 'app') {
done(`Build complete. The ${chalk.cyan(options.outputDir)} directory is ready to be deployed.\n`)
if (options.baseUrl === '/') {
info(`The app is built assuming that it will be deployed at the root of a domain.`)
Expand Down
62 changes: 62 additions & 0 deletions packages/@vue/cli-service/lib/commands/build/resolveLibConfig.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
module.exports = (api, { entry, name }) => {
const genConfig = (format, postfix = format) => {
api.chainWebpack(config => {
const libName = name || api.service.pkg.name

config.entryPoints.clear()
// set proxy entry for *.vue files
if (/\.vue$/.test(entry)) {
config
.entry(`${libName}.${postfix}`)
.add(require.resolve('./entry-lib.js'))
config.resolve
.alias
.set('~entry', api.resolve(entry))
} else {
config
.entry(`${libName}.${postfix}`)
.add(api.resolve(entry))
}

config.output
.filename(`[name].js`)
.library(libName)
.libraryExport('default')
.libraryTarget(format)

// adjust css output name
config
.plugin('extract-css')
.tap(args => {
args[0].filename = `${libName}.css`
return args
})

// only minify min entry
config
.plugin('uglify')
.tap(args => {
args[0].include = /\.min\.js$/
return args
})

// externalize Vue in case user imports it
config
.externals({
vue: {
commonjs: 'vue',
commonjs2: 'vue',
root: 'Vue'
}
})
})

return api.resolveWebpackConfig()
}

return [
genConfig('commonjs2', 'common'),
genConfig('umd'),
genConfig('umd', 'umd.min')
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
module.exports = (api, { entry, name }) => {
const genConfig = postfix => {
api.chainWebpack(config => {
const libName = name || api.service.pkg.name

config.entryPoints.clear()
// set proxy entry for *.vue files
if (/\.vue$/.test(entry)) {
config
.entry(`${libName}.${postfix}`)
.add(require.resolve('./entry-web-component.js'))
config.resolve
.alias
.set('~entry', api.resolve(entry))
} else {
config
.entry(`${libName}.${postfix}`)
.add(api.resolve(entry))
}

config.output
.filename(`[name].js`)

// only minify min entry
config
.plugin('uglify')
.tap(args => {
args[0].include = /\.min\.js$/
return args
})

// externalize Vue in case user imports it
config
.externals({
vue: 'Vue'
})

// TODO handle CSS (insert in shadow DOM)
})

return api.resolveWebpackConfig()
}

return [
genConfig('web-component'),
genConfig('web-component.min')
]
}
156 changes: 156 additions & 0 deletions packages/@vue/cli-service/lib/config/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
// config that are specific to --target app

module.exports = (api, options) => {
api.chainWebpack(webpackConfig => {
// only apply when there's no alternative target
if (process.env.VUE_CLI_TARGET) {
return
}

// inject preload/prefetch to HTML
const PreloadPlugin = require('../webpack/PreloadPlugin')
webpackConfig
.plugin('preload')
.use(PreloadPlugin, [{
rel: 'preload',
include: 'initial',
fileBlacklist: [/\.map$/, /hot-update\.js$/]
}])

webpackConfig
.plugin('prefetch')
.use(PreloadPlugin, [{
rel: 'prefetch',
include: 'asyncChunks'
}])

// HTML plugin
const fs = require('fs')
const htmlPath = api.resolve('public/index.html')
const resolveClientEnv = require('../util/resolveClientEnv')
webpackConfig
.plugin('html')
.use(require('html-webpack-plugin'), [
Object.assign(
fs.existsSync(htmlPath) ? { template: htmlPath } : {},
// expose client env to html template
{ env: resolveClientEnv(options.baseUrl, true /* raw */) }
)
])

if (process.env.NODE_ENV === 'production') {
// minify HTML
webpackConfig
.plugin('html')
.tap(([options]) => [Object.assign(options, {
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
// more options:
// https://github.com/kangax/html-minifier#options-quick-reference
},
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
chunksSortMode: 'dependency'
})])

// Code splitting configs for better long-term caching
// This needs to be updated when upgrading to webpack 4
const CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin')

if (!options.dll) {
// extract vendor libs into its own chunk for better caching, since they
// are more likely to stay the same.
webpackConfig
.plugin('split-vendor')
.use(CommonsChunkPlugin, [{
name: 'vendor',
minChunks (module) {
// any required modules inside node_modules are extracted to vendor
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(`node_modules`) > -1
)
}
}])

// extract webpack runtime and module manifest to its own file in order to
// prevent vendor hash from being updated whenever app bundle is updated
webpackConfig
.plugin('split-manifest')
.use(CommonsChunkPlugin, [{
name: 'manifest',
minChunks: Infinity
}])

// inline the manifest chunk into HTML
webpackConfig
.plugin('inline-manifest')
.use(require('../webpack/InlineSourcePlugin'), [{
include: /manifest\..*\.js$/
}])

// since manifest is inlined, don't preload it anymore
webpackConfig
.plugin('preload')
.tap(([options]) => {
options.fileBlacklist.push(/manifest\..*\.js$/)
return [options]
})
}

// This CommonsChunkPlugin instance extracts shared chunks from async
// chunks and bundles them in a separate chunk, similar to the vendor chunk
// see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk
webpackConfig
.plugin('split-vendor-async')
.use(CommonsChunkPlugin, [{
name: 'app',
async: 'vendor-async',
children: true,
minChunks: 3
}])

// DLL
if (options.dll) {
const webpack = require('webpack')
const UglifyPlugin = require('uglifyjs-webpack-plugin')
const getUglifyOptions = require('./uglifyOptions')
const dllEntries = Array.isArray(options.dll)
? options.dll
: Object.keys(api.service.pkg.dependencies)

webpackConfig
.plugin('dll')
.use(require('autodll-webpack-plugin'), [{
inject: true,
inherit: true,
path: 'js/',
context: api.resolve('.'),
filename: '[name].[hash:8].js',
entry: {
'vendor': [
...dllEntries,
'vue-loader/lib/component-normalizer'
]
},
plugins: [
new webpack.DefinePlugin(resolveClientEnv(options.baseUrl)),
new UglifyPlugin(getUglifyOptions(options))
]
}])
.after('preload')
}

// copy static assets in public/
webpackConfig
.plugin('copy')
.use(require('copy-webpack-plugin'), [[{
from: api.resolve('public'),
to: api.resolve(options.outputDir),
ignore: ['index.html', '.*']
}]])
}
})
}
Loading

0 comments on commit faadadf

Please sign in to comment.