@berun/runner-webpack
Version:
Webpack runner for building React web applications
240 lines (204 loc) • 6.96 kB
text/typescript
import * as path from 'path'
import * as chalk from 'chalk'
import * as fs from 'fs-extra'
import * as bfj from 'bfj'
import * as checkRequiredFiles from 'react-dev-utils/checkRequiredFiles'
import * as FileSizeReporter from 'react-dev-utils/FileSizeReporter'
import { checkBrowsers } from 'react-dev-utils/browsersHelper'
import * as formatWebpackMessages from 'react-dev-utils/formatWebpackMessages'
import verifyPackageTree from './utils/verifyPackageTree'
import printHostingInstructions from './utils/printHostingInstructions'
const webpack = require('webpack')
// MAIN MODULE EXPORTS, WITH DEFAULT FLOW
export default async (berun) => {
try {
await taskBuildPreFlightArgs(berun)
await taskBuildPreFlightChecks(berun)
if (berun.sparkyContext.shouldCompareFileSizes) {
await taskBuildPreFlightMeasure(berun)
}
await taskBuildPreFlightClean(berun)
await taskBuildCopyPublicAssets(berun)
await taskBuildCompile(berun)
if (berun.sparkyContext.shouldCompareFileSizes) {
await taskBuildPostFlightFileSizes(berun)
}
await taskBuildPostFlightInstructions(berun)
} catch (err) {
if (err && err.message) {
console.log(err.message)
}
process.exit(1)
}
}
// EXPORT INDIVIDUAL FUNCTIONS FOR MORE FINE-GRAINED CUSTOM TASKS
// SEE @berun/runner-webpack-static for an example of where these are used
export {
taskBuildPreFlightArgs,
taskBuildPreFlightChecks,
taskBuildPreFlightMeasure,
taskBuildPreFlightClean,
taskBuildCopyPublicAssets,
taskBuildCompile,
taskBuildPostFlightFileSizes,
taskBuildPostFlightInstructions
}
async function taskBuildPreFlightArgs(berun) {
// Process CLI arguments
const argv = process.argv.slice(2)
const writeStatsJson = argv.indexOf('--stats') !== -1
berun.sparkyContext.shouldCompareFileSizes =
berun.sparkyContext.shouldCompareFileSizes !== false
berun.sparkyContext.writeStatsJson = writeStatsJson
berun.sparkyContext.isSPA = berun.sparkyContext.isSPA !== false
}
async function taskBuildPreFlightChecks(berun) {
if (process.env.SKIP_PREFLIGHT_CHECK !== 'true') {
verifyPackageTree()
// Warn and crash if required files are missing
if (
!checkRequiredFiles([
// berun.options.paths.appHtml,
berun.options.paths.appIndexJs
])
) {
process.exit(1)
}
}
// We require that you explictly set browsers and do not fall back to
// browserslist defaults.
await checkBrowsers(berun.options.paths.appPath)
}
async function taskBuildPreFlightMeasure(berun) {
const { measureFileSizesBeforeBuild } = FileSizeReporter
// First, read the current file sizes in build directory.
// This lets us display how much they changed later.
const previousFileSizes = await measureFileSizesBeforeBuild(
berun.options.paths.appBuild
)
berun.sparkyContext.previousFileSizes = previousFileSizes
}
async function taskBuildPreFlightClean(berun) {
// Remove all content but keep the directory so that
// if you're in it, you don't end up in Trash
await fs.emptyDir(berun.options.paths.appBuild)
}
async function taskBuildCopyPublicAssets(berun) {
if (await fs.pathExists(berun.options.paths.appPublic)) {
await fs.copy(berun.options.paths.appPublic, berun.options.paths.appBuild, {
dereference: true,
filter: (file) => file !== berun.options.paths.appHtml
})
}
}
async function taskBuildCompile(berun) {
try {
// Start the webpack build
const { stats, warnings } = await promisifyWebpackBuild(berun)
if (warnings.length) {
console.log(chalk.yellow('Compiled with warnings.\n'))
console.log(warnings.join('\n\n'))
console.log(
`\nSearch for the ${chalk.underline(
chalk.yellow('keywords')
)} to learn more about each warning.`
)
console.log(
`${'To ignore, add '}${chalk.cyan('// eslint-disable-next-line')} ` +
` to the line before.\n`
)
} else {
console.log(chalk.green('Compiled successfully.\n'))
}
berun.sparkyContext.stats = stats
} catch (err) {
console.log(chalk.red('Failed to compile.\n'))
console.log(err.stack)
process.exit(1)
}
}
async function taskBuildPostFlightFileSizes(berun) {
const { stats, previousFileSizes, writeStatsJson } = berun.sparkyContext
if (writeStatsJson) {
await bfj.write(
`${berun.options.paths.appBuild}/bundle-stats.json`,
stats.toJson()
)
}
const { printFileSizesAfterBuild } = FileSizeReporter
// These sizes are pretty large. We'll warn for bundles exceeding them.
const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024
const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024
console.log('File sizes after gzip:\n')
printFileSizesAfterBuild(
stats,
previousFileSizes,
berun.options.paths.appBuild,
WARN_AFTER_BUNDLE_GZIP_SIZE,
WARN_AFTER_CHUNK_GZIP_SIZE
)
}
async function taskBuildPostFlightInstructions(berun) {
const appPackage = require(berun.options.paths.appPackageJson)
const { publicUrl } = berun.options.paths
const publicPath = berun.webpack.output.get('publicPath')
const buildFolder = path.relative(process.cwd(), berun.options.paths.appBuild)
printHostingInstructions(
appPackage,
publicUrl,
publicPath,
buildFolder,
berun.options.paths.useYarn,
berun.sparkyContext.isSPA
)
}
// PRIVATE FUNCTIONS
// Create the production build and print the deployment instructions.
function promisifyWebpackBuild(berun) {
console.log(
berun.sparkyContext.buildMessage ||
'Creating an optimized production build...'
)
const compiler = webpack(berun.webpack.toConfig())
return new Promise<any>((resolve, reject) => {
try {
compiler.run((err, stats) => {
if (err) {
return reject(err)
}
const messages = formatWebpackMessages(stats.toJson({}, true))
if (messages.errors.length) {
// Only keep the first error. Others are often indicative
// of the same problem, but confuse the reader with noise.
if (messages.errors.length > 1) {
messages.errors.length = 1
}
return reject(new Error(messages.errors.join('\n\n')))
}
if (
process.env.CI &&
(typeof process.env.CI !== 'string' ||
process.env.CI.toLowerCase() !== 'false') &&
messages.warnings.length
) {
console.log(
chalk.yellow(
'\nTreating warnings as errors because process.env.CI = true.\n' +
'Most CI servers set it automatically.\n'
)
)
return reject(new Error(messages.warnings.join('\n\n')))
}
const resolveArgs = {
stats,
warnings: messages.warnings
}
return resolve(resolveArgs)
})
} catch (ex) {
console.error(ex)
process.exit(200)
reject(ex)
}
})
}