1 |
|
2 | const util = require('util')
|
3 | const fs = require('fs')
|
4 | const url = require('url')
|
5 | const chalk = require('chalk')
|
6 | const notifier = require('node-notifier')
|
7 | const co = require('co')
|
8 | const stripAnsi = require('strip-ansi')
|
9 | const tildify = require('tildify')
|
10 | const merge = require('lodash/merge')
|
11 | const opn = require('opn')
|
12 | const buildConfigChain = require('babel-core/lib/transformation/file/options/build-config-chain')
|
13 | const LoadExternalConfig = require('poi-load-config')
|
14 | const loadPoiConfig = require('poi-load-config/poi')
|
15 | const AppError = require('../lib/app-error')
|
16 | const { cwd, ownDir, inferHTML, readPkg, unspecifiedAddress } = require('../lib/utils')
|
17 | const poi = require('../lib')
|
18 | const logger = require('../lib/logger')
|
19 |
|
20 | module.exports = co.wrap(function * (cliOptions) {
|
21 | console.log(`> Running in ${cliOptions.mode} mode`)
|
22 | if (!process.env.NODE_ENV) {
|
23 |
|
24 | process.env.NODE_ENV = cliOptions.mode === 'watch' ? 'development' : cliOptions.mode
|
25 | }
|
26 |
|
27 | let { path: configPath, config = {} } = yield loadPoiConfig({ config: cliOptions.config })
|
28 |
|
29 | if (configPath) {
|
30 | console.log(`> Using external Poi config file`)
|
31 | console.log(chalk.dim(`> location: "${tildify(configPath)}"`))
|
32 | config = handleConfig(config, cliOptions)
|
33 | } else if (cliOptions.config) {
|
34 | throw new AppError('Config file was not found!')
|
35 | }
|
36 |
|
37 | const options = merge(config, cliOptions)
|
38 |
|
39 | const loadExternalConfig = new LoadExternalConfig({ cwd: options.cwd })
|
40 |
|
41 | if (options.babel === undefined) {
|
42 | const { useConfig, file } = yield loadExternalConfig.babel(buildConfigChain)
|
43 | if (useConfig) {
|
44 | console.log('> Using external babel configuration')
|
45 | console.log(chalk.dim(`> location: "${tildify(file)}"`))
|
46 | options.babel = {
|
47 | cacheDirectory: true,
|
48 | babelrc: true
|
49 | }
|
50 | } else {
|
51 | options.babel = {
|
52 | cacheDirectory: true,
|
53 | babelrc: false
|
54 | }
|
55 | }
|
56 | if (options.babel.babelrc === false) {
|
57 |
|
58 | options.babel.presets = [
|
59 | [require.resolve('babel-preset-vue-app'), { useBuiltIns: true }]
|
60 | ]
|
61 | }
|
62 | }
|
63 |
|
64 | if (options.postcss === undefined) {
|
65 | const postcssConfig = yield loadExternalConfig.postcss()
|
66 | if (postcssConfig.file) {
|
67 | console.log('> Using extenal postcss configuration')
|
68 | console.log(chalk.dim(`> location: "${tildify(postcssConfig.file)}"`))
|
69 | options.postcss = postcssConfig
|
70 | }
|
71 | }
|
72 |
|
73 | if (options.html === undefined) {
|
74 | console.log(`> Using inferred value from package.json for HTML file`)
|
75 | options.html = inferHTML(options)
|
76 | }
|
77 |
|
78 | if (options.entry === undefined) {
|
79 | const mainField = readPkg().main
|
80 | if (mainField) {
|
81 | console.log(`> Using main field in package.json as entry point`)
|
82 | options.entry = mainField
|
83 | }
|
84 | }
|
85 |
|
86 | if (options.homepage === undefined && options.mode === 'production') {
|
87 | options.homepage = readPkg().homepage
|
88 | }
|
89 |
|
90 | const { browserslist = ['ie > 8', 'last 2 versions'] } = readPkg()
|
91 |
|
92 | options.autoprefixer = options.autoprefixer === false ? false : Object.assign({
|
93 | browsers: browserslist
|
94 | }, options.autoprefixer)
|
95 |
|
96 | deleteExtraOptions(options, [
|
97 | '_',
|
98 | '$0',
|
99 | 'inspectOptions',
|
100 | 'inspect-options',
|
101 | 'v',
|
102 | 'version',
|
103 | 'h',
|
104 | 'help'
|
105 | ])
|
106 |
|
107 | if (cliOptions.inspectOptions) {
|
108 | console.log('> Options:', util.inspect(options, { colors: true, depth: null }))
|
109 | }
|
110 |
|
111 | const app = poi(options)
|
112 |
|
113 | console.log(`> Bundling with Webpack ${require('webpack/package.json').version}`)
|
114 |
|
115 | if (options.mode === 'production') {
|
116 | console.log('> Creating an optimized production build:\n')
|
117 | const stats = yield app.build()
|
118 | if (options.generateStats) {
|
119 | const statsFile = cwd(options.cwd, typeof options.generateStats === 'string' ? options.generateStats : 'stats.json')
|
120 | console.log('> Generating webpack stats file')
|
121 | fs.writeFileSync(statsFile, JSON.stringify(stats.toJson()), 'utf8')
|
122 | console.log(chalk.dim(`> location: "${tildify(statsFile)}"`))
|
123 | }
|
124 | } else if (options.mode === 'watch') {
|
125 | yield app.watch()
|
126 | } else if (options.mode === 'development') {
|
127 | const { server, host, port } = yield app.dev()
|
128 |
|
129 | server.listen(port, host)
|
130 | .on('error', err => {
|
131 | if (err.code === 'EADDRINUSE') {
|
132 | return handleError(new AppError(`Port ${port} is already in use.\n\nYou can use another one by adding \`--port <port>\` or set it in config file.`))
|
133 | }
|
134 | handleError(err)
|
135 | })
|
136 |
|
137 | app.once('compile-done', () => {
|
138 | if (options.open) {
|
139 | opn(url.format({
|
140 | protocol: 'http',
|
141 | hostname: unspecifiedAddress(host) ? 'localhost' : host,
|
142 | port
|
143 | }))
|
144 | }
|
145 | })
|
146 | } else if (options.mode === 'test') {
|
147 | app.test().catch(handleError)
|
148 | }
|
149 | })
|
150 |
|
151 | module.exports.handleError = handleError
|
152 |
|
153 | function handleError(err) {
|
154 | console.log()
|
155 | if (err.name === 'AppError') {
|
156 | console.error(chalk.red(err.message))
|
157 | } else {
|
158 | console.error(err.stack.trim())
|
159 | }
|
160 | notifier.notify({
|
161 | title: 'Poi: error!',
|
162 | message: stripAnsi(err.stack).replace(/^\s+/gm, ''),
|
163 | icon: ownDir('bin/error.png')
|
164 | })
|
165 | console.log()
|
166 | logger.error('Failed to start!')
|
167 | console.log()
|
168 | process.exit(1)
|
169 | }
|
170 |
|
171 | function handleConfig(config, options) {
|
172 | if (typeof config === 'function') {
|
173 | config = config(options, require)
|
174 | }
|
175 |
|
176 | config = merge(config, config[options.mode])
|
177 |
|
178 | delete config.development
|
179 | delete config.production
|
180 | delete config.watch
|
181 | delete config.test
|
182 |
|
183 | return config
|
184 | }
|
185 |
|
186 | function deleteExtraOptions(obj, arr) {
|
187 | arr.forEach(k => delete obj[k])
|
188 | }
|