poi
Version:
A zero-config bundler for JavaScript applications.
168 lines (139 loc) • 5.35 kB
JavaScript
exports.name = 'builtin:serve'
exports.cli = api => {
const { command, args } = api
command.option('-s, --serve', 'Serve assets on a local server')
if (!args.has('s') && !args.has('serve')) return
command
.option('-p, --port <port>', 'Server port', { default: '4000' })
.option('--host <host>', 'Server host', { default: '0.0.0.0' })
.option('--proxy <url>', 'Proxy API requests')
.option('--no-hot', 'Disable hot reloading')
.option('-o, --open', 'Open in browser')
command.action(async () => {
const devServer = Object.assign({}, api.config.devServer)
delete devServer.hotEntries
const { https, host, port: _port, open } = devServer
const port = await require('get-port')({ port: _port, host })
const webpackConfig = api.createWebpackChain().toConfig()
// No need to print URLs in test mode
if (api.mode !== 'test') {
webpackConfig.plugins.push({
apply(compiler) {
let isFirstBuild = true
// TODO: figure out why using .tap() can't catch error
compiler.hooks.done.tap('print-serve-urls', stats => {
if (stats.hasErrors() || stats.hasWarnings()) return
require('@poi/dev-utils/printServeMessage')({
https,
host,
port,
expectedPort: _port,
open,
isFirstBuild,
publicUrl: api.config.output.publicUrl
})
isFirstBuild = false
})
}
})
}
const compiler = api.createWebpackCompiler(webpackConfig)
const devServerConfig = Object.assign(
{
noInfo: true,
historyApiFallback: true,
overlay: false,
disableHostCheck: true,
compress: true,
// Silence WebpackDevServer's own logs since they're generally not useful.
// It will still show compile warnings and errors with this setting.
clientLogLevel: 'none',
// Prevent a WS client from getting injected as we're already including
// `webpackHotDevClient`.
injectClient: false,
publicPath: webpackConfig.output.publicPath,
contentBase:
api.config.publicFolder && api.resolveCwd(api.config.publicFolder),
watchContentBase: true,
stats: 'none'
},
devServer,
{
proxy:
typeof devServer.proxy === 'string'
? require('@poi/dev-utils/prepareProxy')(
devServer.proxy,
api.resolveCwd(api.config.publicFolder),
api.cli.options.debug
)
: devServer.proxy
}
)
if (devServerConfig.publicPath && devServerConfig.historyApiFallback) {
devServerConfig.historyApiFallback = {
index: `${devServerConfig.publicPath}/index.html`.replace(/\/+/, '/'),
...devServerConfig.historyApiFallback
}
}
const existingBefore = devServerConfig.before
devServerConfig.before = server => {
api.hooks.invoke('beforeDevMiddlewares', server)
server.use(
require('@poi/dev-utils/launchEditorEndpoint'),
require('launch-editor-middleware')(() =>
console.log(
`To specify an editor, sepcify the EDITOR env variable or ` +
`add "editor" field to your Vue project config.\n`
)
)
)
server.use(require('@poi/dev-utils/skipServiceWorker')())
existingBefore && existingBefore(server)
}
const exitingAfter = devServerConfig.after
devServerConfig.after = server => {
exitingAfter && exitingAfter(server)
api.hooks.invoke('onCreateServer', server) // TODO: remove this in the future
api.hooks.invoke('afterDevMiddlewares', server)
}
api.hooks.invoke('createDevServerConfig', devServerConfig)
const WebpackDevServer = require('webpack-dev-server')
const server = new WebpackDevServer(compiler, devServerConfig)
api.hooks.invoke('createServer', { server, port, host })
server.listen(port, host)
})
}
exports.apply = api => {
api.hook('createWebpackChain', config => {
if (!api.cli.options.serve) return
if (api.config.devServer.hot) {
const hotEntries =
api.config.devServer.hotEntries.length > 0
? api.config.devServer.hotEntries
: config.entryPoints.store.keys()
for (const entry of hotEntries) {
if (config.entryPoints.has(entry)) {
config.entry(entry).prepend('#webpack-hot-client')
}
}
const { HotModuleReplacementPlugin } = require('webpack')
HotModuleReplacementPlugin.__expression = `require('webpack').HotModuleReplacementPlugin`
config.plugin('hot').use(HotModuleReplacementPlugin)
}
// Point sourcemap entries to original disk location (format as URL on Windows)
// Useful for react-error-overlay
config.output.devtoolModuleFilenameTemplate(info =>
info.absoluteResourcePath.replace(/\\/g, '/')
)
// Don't show bundled files in --serve
if (config.plugins.has('print-status')) {
config.plugin('print-status').tap(([options]) => [
Object.assign(options, {
printFileStats: false
})
])
}
// Don't copy public folder, since we serve it instead
config.plugins.delete('copy-public-folder')
})
}