UNPKG

7.18 kBJavaScriptView Raw
1/* eslint-disable unicorn/no-process-exit */
2const fs = require('fs')
3const url = require('url')
4const chalk = require('chalk')
5const notifier = require('node-notifier')
6const co = require('co')
7const stripAnsi = require('strip-ansi')
8const tildify = require('tildify')
9const address = require('address')
10const merge = require('lodash.merge')
11const findBabelConfig = require('babel-load-config')
12const findPostcssConfig = require('postcss-load-config')
13const copy = require('clipboardy')
14const opn = require('opn')
15const AppError = require('../lib/app-error')
16const { cwd, ownDir, inferHTML, readPkg, parsePresets } = require('../lib/utils')
17const loadConfig = require('../lib/load-config')
18const poi = require('../lib')
19const terminal = require('../lib/terminal-utils')
20const logger = require('../lib/logger')
21
22const unspecifiedAddress = host => host === '0.0.0.0' || host === '::'
23
24const loadPostCSSConfig = co.wrap(function * () {
25 let defaultPostcssOptions = {}
26 try {
27 defaultPostcssOptions = yield findPostcssConfig({}, null, { argv: false })
28 .then(res => {
29 console.log('> Using extenal postcss configuration')
30 console.log(chalk.dim(`> location: "${tildify(res.file)}"`))
31 return res
32 })
33 } catch (err) {
34 if (err.message.indexOf('No PostCSS Config found') === -1) {
35 throw err
36 }
37 }
38
39 return defaultPostcssOptions
40})
41
42const loadBabelConfig = function () {
43 const defaultBabelOptions = {
44 babelrc: true,
45 cacheDirectory: true
46 }
47
48 const externalBabelConfig = findBabelConfig(process.cwd())
49 if (externalBabelConfig) {
50 console.log('> Using external babel configuration')
51 console.log(chalk.dim(`> location: "${tildify(externalBabelConfig.loc)}"`))
52 // It's possible to turn off babelrc support via babelrc itself.
53 // In that case, we should add our default preset.
54 // That's why we need to do this.
55 const { options } = externalBabelConfig
56 defaultBabelOptions.babelrc = options.babelrc !== false
57 } else {
58 defaultBabelOptions.babelrc = false
59 }
60
61 // Add our default preset if the no "babelrc" found.
62 if (!defaultBabelOptions.babelrc) {
63 defaultBabelOptions.presets = [
64 [require.resolve('babel-preset-vue-app'), {
65 useBuiltIns: true
66 }]
67 ]
68 }
69
70 return defaultBabelOptions
71}
72
73module.exports = co.wrap(function * (cliOptions) {
74 console.log(`> Running in ${cliOptions.mode} mode`)
75
76 const config = yield loadConfig(cliOptions)
77 const options = merge(config, cliOptions)
78
79 const clear = () => options.clear !== false && terminal.clear()
80
81 const printStats = stats => {
82 clear()
83 if (stats.hasErrors() || stats.hasWarnings()) {
84 console.log(stats.toString('errors-only').trim())
85 process.exitCode = 1
86 } else {
87 console.log(stats.toString({
88 colors: true,
89 chunks: false,
90 modules: false,
91 children: false,
92 version: false,
93 hash: false,
94 timings: false
95 }))
96 process.exitCode = 0
97 }
98 }
99
100 let copied
101 let lanIP
102
103 const printOutro = (stats, host, port) => {
104 console.log()
105 if (stats.hasErrors()) {
106 logger.error('Compiled with Errors!')
107 } else if (stats.hasWarnings()) {
108 logger.warn('Compiled with Warnings!')
109 } else {
110 if (options.mode === 'development') {
111 const isUnspecifiedAddress = unspecifiedAddress(host)
112 const localURL = url.format({
113 protocol: 'http',
114 hostname: isUnspecifiedAddress ? 'localhost' : host,
115 port
116 })
117 if (copied) {
118 console.log(chalk.bold(`> Open ${localURL}`))
119 } else {
120 copied = true
121 try {
122 copy.writeSync(localURL)
123 console.log(chalk.bold(`> Open ${localURL}`), chalk.dim('(copied!)'))
124 } catch (err) {
125 console.log(chalk.bold(`> Open ${localURL}`))
126 }
127 }
128 if (isUnspecifiedAddress) {
129 const lanURL = url.format({
130 protocol: 'http',
131 hostname: lanIP || (lanIP = address.ip()),
132 port
133 })
134 console.log(chalk.dim(`> On Your Network: ${lanURL}`))
135 }
136 console.log()
137 }
138 logger.success(`Build ${stats.hash.slice(0, 6)} finished in ${stats.endTime - stats.startTime} ms!`)
139 }
140 console.log()
141 }
142
143 if (options.presets) {
144 options.presets = parsePresets(options.presets)
145 }
146
147 if (options.babel === undefined) {
148 options.babel = loadBabelConfig()
149 }
150
151 if (options.postcss === undefined) {
152 options.postcss = yield loadPostCSSConfig()
153 }
154
155 if (options.html === undefined) {
156 console.log(`> Using inferred value from package.json for HTML file`)
157 options.html = inferHTML(options)
158 }
159
160 if (options.entry === undefined) {
161 const mainField = readPkg().main
162 if (mainField) {
163 console.log(`> Using main field in package.json as entry point`)
164 options.entry = mainField
165 }
166 }
167
168 if (options.homepage === undefined && options.mode === 'production') {
169 options.homepage = readPkg().homepage
170 }
171
172 const app = poi(options)
173
174 console.log(`> Bundling with Webpack ${require('webpack/package.json').version}`)
175
176 if (options.mode === 'production') {
177 clear()
178 console.log('> Creating an optimized production build:\n')
179 const stats = yield app.build()
180 printStats(stats)
181 printOutro(stats)
182 if (options.generateStats) {
183 const statsFile = cwd(options.cwd, typeof options.generateStats === 'string' ? options.generateStats : 'stats.json')
184 console.log('> Generating webpack stats file')
185 fs.writeFileSync(statsFile, JSON.stringify(stats.toJson()), 'utf8')
186 console.log(chalk.dim(`> location: "${tildify(statsFile)}"`))
187 }
188 } else if (options.mode === 'watch') {
189 yield app.watch()
190 app.once('compile-done', () => {
191 console.log()
192 })
193 app.on('compile-done', stats => {
194 printStats(stats)
195 printOutro(stats)
196 })
197 } else if (options.mode === 'development') {
198 const { server, host, port } = yield app.dev()
199
200 server.listen(port, host)
201 .on('error', err => {
202 if (err.code === 'EADDRINUSE') {
203 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.`))
204 }
205 handleError(err)
206 })
207
208 app.once('compile-done', () => {
209 if (options.open) {
210 opn(url.format({
211 protocol: 'http',
212 hostname: unspecifiedAddress(host) ? 'localhost' : host,
213 port
214 }))
215 }
216 })
217
218 app.on('compile-done', stats => {
219 printStats(stats)
220 printOutro(stats, host, port)
221 })
222 } else if (options.mode === 'test') {
223 app.test().catch(handleError)
224 }
225})
226
227module.exports.handleError = handleError
228
229function handleError(err) {
230 console.log()
231 if (err.name === 'AppError') {
232 console.error(chalk.red(err.message))
233 } else {
234 console.error(err.stack.trim())
235 }
236 notifier.notify({
237 title: 'Poi: error!',
238 message: stripAnsi(err.stack).replace(/^\s+/gm, ''),
239 icon: ownDir('bin/error.png')
240 })
241 console.log()
242 logger.error('Failed to start!')
243 console.log()
244 process.exit(1)
245}