Skip to content

Commit 831473f

Browse files
egoistyyx990803
authored andcommitted
Add vue build command, closed #173 (#287)
* add vue-build * add production mode and custom config * fix lint * set NODE_ENV * add css loaders and extracting support * fix test * mock-template-build should be removed * support using SFC directly * fail loudly * add test for vue build * update deps * support local config files * tweak test * tweak test again * expose devServer * allow custom config directory * read devServer from webpack config * fix test * add docs * use postcss everywhere, add autoprefixer * compatible with yarn * fix link to buble * add option to open browser * remove dist files in production mode * support static files like images * add template option * fix typo * add test for local config directory * tweak docs * check if entry exists * explain how it works * add comments in default entry * tweak doc * fix entry * handle webpack error * minor tweaks * use merge instead of assign for cli options * allow to directly set path to config file * update doc * update doc for using babel * replace boom with something more accurate * clean up * add link to build.md in readme * show cli help if no entry * tweak test description * use custom server, add proxy option * add setup option * upgrade deps * switch to babel * update doc for babel * add alias for --mount * add message for successful builds * expose `production` in options * enable mount for .vue file, add --lib option
1 parent 68e8080 commit 831473f

16 files changed

+6339
-8
lines changed

.editorconfig

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
root = true
2+
3+
[*]
4+
indent_style = space
5+
indent_size = 2
6+
end_of_line = lf
7+
charset = utf-8
8+
trim_trailing_whitespace = true
9+
insert_final_newline = true
10+
11+
[*.md]
12+
trim_trailing_whitespace = false

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
.DS_Store
22
node_modules
33
test/e2e/mock-template-build
4+
*.log
5+
dist/

README.md

+4
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ $ vue init webpack my-project
2424

2525
The above command pulls the template from [vuejs-templates/webpack](https://github.com/vuejs-templates/webpack), prompts for some information, and generates the project at `./my-project/`.
2626

27+
### vue build
28+
29+
Use vue-cli as a zero-configuration development tool for your Vue apps and component, check out the [docs](/docs/build.md).
30+
2731
### Official Templates
2832

2933
The purpose of official Vue project templates are to provide opinionated, battery-included development tooling setups so that users can get started with actual app code as fast as possible. However, these templates are un-opinionated in terms of how you structure your app code and what libraries you use in addition to Vue.js.

bin/vue

+1
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ require('commander')
55
.usage('<command> [options]')
66
.command('init', 'generate a new project from a template')
77
.command('list', 'list available official templates')
8+
.command('build', 'prototype a new project')
89
.parse(process.argv)

bin/vue-build

+359
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,359 @@
1+
#!/usr/bin/env node
2+
3+
var fs = require('fs')
4+
var path = require('path')
5+
var program = require('commander')
6+
var chalk = require('chalk')
7+
var rm = require('rimraf').sync
8+
var home = require('user-home')
9+
var webpack = require('webpack')
10+
var webpackMerge = require('webpack-merge')
11+
var HtmlWebpackPlugin = require('html-webpack-plugin')
12+
var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
13+
var PostCompilePlugin = require('post-compile-webpack-plugin')
14+
var ProgressPlugin = require('webpack/lib/ProgressPlugin')
15+
var ExtractTextPlugin = require('extract-text-webpack-plugin')
16+
var isYarn = require('installed-by-yarn-globally')
17+
var camelcase = require('camelcase')
18+
var tildify = require('tildify')
19+
var loaders = require('../lib/loaders')
20+
var logger = require('../lib/logger')
21+
var createServer = require('../lib/server')
22+
23+
/**
24+
* Usage.
25+
*/
26+
27+
program
28+
.usage('[entry]')
29+
.option('--dist [directory]', 'Output directory for bundled files')
30+
.option('--port [port]', 'Server port')
31+
.option('--host [host]', 'Server host', 'localhost')
32+
.option('--prod, --production', 'Build for production')
33+
.option('-m, --mount', 'Auto-create app instance from given component, true for `.vue` file')
34+
.option('-c, --config [file]', 'Use custom config file')
35+
.option('-w, --webpack [file]', 'Use custom webpack config file')
36+
.option('--disable-config', 'You do not want to use config file')
37+
.option('--disable-webpack-config', 'You do not want to use webpack config file')
38+
.option('-o, --open', 'Open browser')
39+
.option('--proxy', 'Proxy API request')
40+
.option('--lib [libraryName]', 'Distribute component in UMD format')
41+
.parse(process.argv)
42+
43+
var args = program.args
44+
var production = program.production
45+
46+
process.env.NODE_ENV = production ? 'production' : 'development'
47+
48+
var localConfig
49+
50+
// config option in only available in CLI option
51+
if (!program.disableConfig) {
52+
var localConfigPath = typeof program.config === 'string'
53+
? path.join(process.cwd(), program.config)
54+
: path.join(home, '.vue', 'config.js')
55+
var hasLocalConfig = fs.existsSync(localConfigPath)
56+
if (hasLocalConfig) {
57+
console.log(`> Using config file at ${chalk.yellow(tildify(localConfigPath))}`)
58+
localConfig = require(localConfigPath)
59+
}
60+
}
61+
62+
var options = merge({
63+
port: 4000,
64+
dist: 'dist',
65+
host: 'localhost'
66+
}, localConfig, {
67+
entry: args[0],
68+
port: program.port,
69+
host: program.host,
70+
open: program.open,
71+
dist: program.dist,
72+
webpack: program.webpack,
73+
disableWebpackConfig: program.disableWebpackConfig,
74+
mount: program.mount,
75+
proxy: program.proxy,
76+
production: program.production,
77+
lib: program.lib
78+
})
79+
80+
function help () {
81+
if (!options.entry) {
82+
return program.help()
83+
}
84+
}
85+
help()
86+
87+
var cssOptions = {
88+
extract: production,
89+
sourceMap: true
90+
}
91+
92+
var postcssOptions = {
93+
plugins: [
94+
require('autoprefixer')(Object.assign({
95+
browsers: ['ie > 8', 'last 5 versions']
96+
}, options.autoprefixer))
97+
]
98+
}
99+
100+
if (options.postcss) {
101+
if (Object.prototype.toString.call(options.postcss) === '[object Object]') {
102+
var plugins = options.postcss.plugins
103+
if (plugins) {
104+
postcssOptions.plugins = postcssOptions.plugins.concat(plugins)
105+
delete options.postcss.plugins
106+
}
107+
Object.assign(postcssOptions, options.postcss)
108+
} else {
109+
postcssOptions = options.postcss
110+
}
111+
}
112+
113+
var babelOptions = {
114+
babelrc: true,
115+
cacheDirectory: true,
116+
sourceMaps: production ? 'both' : false,
117+
presets: []
118+
}
119+
120+
var hasBabelRc = fs.existsSync('.babelrc')
121+
if (hasBabelRc) {
122+
console.log('> Using .babelrc in current working directory')
123+
} else {
124+
babelOptions.presets.push(require.resolve('babel-preset-vue-app'))
125+
}
126+
127+
var webpackConfig = {
128+
entry: {
129+
client: []
130+
},
131+
output: {
132+
path: path.join(process.cwd(), options.dist),
133+
filename: production ? '[name].[chunkhash:8].js' : '[name].js',
134+
publicPath: '/'
135+
},
136+
performance: {
137+
hints: false
138+
},
139+
resolve: {
140+
extensions: ['.js', '.vue', '.css'],
141+
modules: [
142+
process.cwd(),
143+
path.join(process.cwd(), 'node_modules'), // modules in cwd's node_modules
144+
path.join(__dirname, '../node_modules') // modules in package's node_modules
145+
],
146+
alias: {}
147+
},
148+
resolveLoader: {
149+
modules: [
150+
path.join(process.cwd(), 'node_modules'), // loaders in cwd's node_modules
151+
path.join(__dirname, '../node_modules') // loaders in package's node_modules
152+
]
153+
},
154+
module: {
155+
rules: [
156+
{
157+
test: /\.js$/,
158+
loader: 'babel-loader',
159+
exclude: [/node_modules/]
160+
},
161+
{
162+
test: /\.vue$/,
163+
loader: 'vue-loader',
164+
options: {
165+
postcss: postcssOptions,
166+
loaders: loaders.cssLoaders(cssOptions)
167+
}
168+
},
169+
{
170+
test: /\.(ico|jpg|png|gif|svg|eot|otf|webp|ttf|woff|woff2)(\?.*)?$/,
171+
loader: 'file-loader',
172+
query: {
173+
name: options.lib ? 'static/[name].[ext]' : 'static/[name].[hash:8].[ext]'
174+
}
175+
}
176+
].concat(loaders.styleLoaders(cssOptions))
177+
},
178+
plugins: [
179+
new webpack.DefinePlugin({
180+
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
181+
}),
182+
new webpack.LoaderOptionsPlugin({
183+
options: {
184+
context: process.cwd(),
185+
postcss: postcssOptions,
186+
babel: babelOptions
187+
}
188+
})
189+
]
190+
}
191+
192+
// if entry ends with `.vue` and no `mount` option was specified
193+
// we implicitly set `mount` to true unless `lib` is set
194+
// for `.js` component you can set `mount` to true manually
195+
if (options.mount === undefined && !options.lib && /\.vue$/.test(options.entry)) {
196+
options.mount = true
197+
}
198+
// create a Vue instance to load given component
199+
// set an alias to the path of the component
200+
// otherwise use it directly as webpack entry
201+
if (options.mount) {
202+
webpackConfig.entry.client.push(path.join(__dirname, '../lib/default-entry'))
203+
webpackConfig.resolve.alias['your-tasteful-component'] = options.entry
204+
} else {
205+
webpackConfig.entry.client.push(options.entry)
206+
}
207+
208+
// the `lib` mode
209+
// distribute the entry file as a component (umd format)
210+
if (options.lib) {
211+
webpackConfig.output.filename = replaceExtension(options.entry, '.js')
212+
webpackConfig.output.library = typeof options.lib === 'string'
213+
? options.lib
214+
: camelcase(replaceExtension(options.entry, ''))
215+
webpackConfig.output.libraryTarget = 'umd'
216+
} else {
217+
// only output index.html in non-lib mode
218+
webpackConfig.plugins.unshift(
219+
new HtmlWebpackPlugin(Object.assign({
220+
title: 'Vue App',
221+
template: path.join(__dirname, '../lib/template.html')
222+
}, options.html))
223+
)
224+
}
225+
226+
// installed by `yarn global add`
227+
if (isYarn(__dirname)) {
228+
// modules in yarn global node_modules
229+
// because of yarn's flat node_modules structure
230+
webpackConfig.resolve.modules.push(path.join(__dirname, '../../'))
231+
// loaders in yarn global node_modules
232+
webpackConfig.resolveLoader.modules.push(path.join(__dirname, '../../'))
233+
}
234+
235+
if (production) {
236+
webpackConfig.devtool = 'source-map'
237+
webpackConfig.plugins.push(
238+
new ProgressPlugin(),
239+
new webpack.optimize.UglifyJsPlugin({
240+
sourceMap: true,
241+
compressor: {
242+
warnings: false
243+
},
244+
output: {
245+
comments: false
246+
}
247+
}),
248+
new webpack.LoaderOptionsPlugin({
249+
minimize: true
250+
}),
251+
new ExtractTextPlugin(options.lib ? `${replaceExtension(options.entry, '.css')}` : '[name].[contenthash:8].css')
252+
)
253+
} else {
254+
webpackConfig.devtool = 'eval-source-map'
255+
webpackConfig.entry.client.unshift(
256+
require.resolve('webpack-hot-middleware/client') + '?reload=true'
257+
)
258+
webpackConfig.plugins.push(
259+
new webpack.HotModuleReplacementPlugin(),
260+
new FriendlyErrorsPlugin(),
261+
new PostCompilePlugin(() => {
262+
console.log(`> Open http://${options.host}:${options.port}`)
263+
})
264+
)
265+
}
266+
267+
// webpack
268+
// object: merge with webpack config
269+
// function: mutate webpack config
270+
// string: load webpack config file then merge with webpack config
271+
if (!options.disableWebpackConfig) {
272+
if (typeof options.webpack === 'object') {
273+
webpackConfig = webpackMerge.smart(webpackConfig, options.webpack)
274+
} else if (typeof options.webpack === 'function') {
275+
webpackConfig = options.webpack(webpackConfig, options, webpack)
276+
} else {
277+
var localWebpackConfigPath = typeof options.webpack === 'string'
278+
? path.join(process.cwd(), options.webpack)
279+
: path.join(home, '.vue', 'webpack.config.js')
280+
var hasLocalWebpackConfig = fs.existsSync(localWebpackConfigPath)
281+
if (hasLocalWebpackConfig) {
282+
console.log(`> Using webpack config file at ${chalk.yellow(tildify(localWebpackConfigPath))}`)
283+
webpackConfig = webpackMerge.smart(webpackConfig, require(localWebpackConfigPath))
284+
}
285+
}
286+
}
287+
288+
try {
289+
var compiler = webpack(webpackConfig)
290+
} catch (err) {
291+
if (err.name === 'WebpackOptionsValidationError') {
292+
logger.fatal(err.message)
293+
} else {
294+
throw err
295+
}
296+
}
297+
298+
checkEntryExists(options.entry)
299+
300+
if (production) {
301+
console.log('> Creating an optimized production build:\n')
302+
// remove dist files but keep that folder in production mode
303+
rm(path.join(options.dist, '*'))
304+
compiler.run((err, stats) => {
305+
if (err) {
306+
process.exitCode = 1
307+
return console.error(err.stack)
308+
}
309+
if (stats.hasErrors() || stats.hasWarnings()) {
310+
process.exitCode = 1
311+
return console.error(stats.toString('errors-only'))
312+
}
313+
console.log(stats.toString({
314+
chunks: false,
315+
children: false,
316+
modules: false,
317+
colors: true
318+
}))
319+
console.log(`\n${chalk.bgGreen.black(' SUCCESS ')} Compiled successfully.\n`)
320+
console.log(`The ${chalk.cyan(options.dist)} folder is ready to be deployed.`)
321+
console.log(`You may also serve it locally with a static server:\n`)
322+
console.log(` ${chalk.yellow('npm')} i -g serve`)
323+
console.log(` ${chalk.yellow('serve')} ${options.dist}\n`)
324+
})
325+
} else {
326+
var server = createServer(compiler, options)
327+
328+
server.listen(options.port, options.host)
329+
if (options.open) {
330+
require('opn')(`http://${options.host}:${options.port}`)
331+
}
332+
}
333+
334+
function checkEntryExists (entry) {
335+
if (!fs.existsSync(entry)) {
336+
logger.fatal(`${chalk.yellow(entry)} does not exist, did you forget to create one?`)
337+
}
338+
}
339+
340+
function merge (obj) {
341+
var i = 1
342+
var target
343+
var key
344+
345+
for (; i < arguments.length; i++) {
346+
target = arguments[i]
347+
for (key in target) {
348+
if (Object.prototype.hasOwnProperty.call(target, key) && target[key] !== undefined) {
349+
obj[key] = target[key]
350+
}
351+
}
352+
}
353+
354+
return obj
355+
}
356+
357+
function replaceExtension (str, ext) {
358+
return str.replace(/\.(vue|js)$/, ext)
359+
}

0 commit comments

Comments
 (0)