UNPKG

5.67 kBJavaScriptView Raw
1/* eslint-disable unicorn/no-process-exit */
2const util = require('util')
3const fs = require('fs')
4const url = require('url')
5const chalk = require('chalk')
6const notifier = require('node-notifier')
7const co = require('co')
8const stripAnsi = require('strip-ansi')
9const tildify = require('tildify')
10const merge = require('lodash/merge')
11const opn = require('opn')
12const buildConfigChain = require('babel-core/lib/transformation/file/options/build-config-chain')
13const LoadExternalConfig = require('poi-load-config')
14const loadPoiConfig = require('poi-load-config/poi')
15const AppError = require('../lib/app-error')
16const { cwd, ownDir, inferHTML, readPkg, unspecifiedAddress } = require('../lib/utils')
17const poi = require('../lib')
18const logger = require('../lib/logger')
19
20module.exports = co.wrap(function * (cliOptions) {
21 console.log(`> Running in ${cliOptions.mode} mode`)
22 if (!process.env.NODE_ENV) {
23 // env could be `production` `development` `test`
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 // Use our default preset when no babelrc was found
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
151module.exports.handleError = handleError
152
153function 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
171function 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
186function deleteExtraOptions(obj, arr) {
187 arr.forEach(k => delete obj[k])
188}