虽然在开发中使用npm run start
一切似乎在 webpack 构建速度方面都很好,但在生产中,运行npm run build
时构建应用程序需要超过 5 分钟。我将构建速度与旧版本的应用程序以及干净的 RSK 构建进行了比较,我认为减速可能是由sass-loader引起的,因此对构建进行了分析并查看了http://webpack.github.io/分析会告诉我。
似乎 sass-loader 确实在这里出错了。我尝试使用fast-sass-loader但这增加了构建时间而不是减少它。像分析工具建议的那样预取加载器也没有帮助。
这是它正在使用的package.json
和webpack.config.js
文件。
包.json
{
"name": "web",
"version": "2.14.7",
"private": true,
"engines": {
"node": ">='6.5'",
"npm": ">=3.10"
},
"browserslist": [
">1%",
"last 4 versions",
"Firefox ESR",
"not ie < 9"
],
"dependencies": {
"apollo-client": "^1.7.0",
"axios": "^0.15.3",
"babel-plugin-add-module-exports": "^0.2.1",
"babel-polyfill": "^6.22.0",
"bcrypt": "^1.0.2",
"bcryptjs": "^2.4.3",
"bluebird": "^3.5.1",
"body-parser": "^1.16.0",
"bootstrap": "4.0.0-beta",
"bugsnag": "^2.0.0",
"classnames": "^2.2.5",
"compression": "^1.7.1",
"connect-ensure-login": "^0.1.1",
"cookie-parser": "^1.4.3",
"core-js": "^2.4.1",
"express": "^4.16.2",
"express-graphql": "^0.6.3",
"express-jwt": "^5.1.0",
"express-request-language": "^1.1.9",
"express-session": "^1.15.6",
"fastclick": "^1.0.6",
"fb": "^2.0.0",
"fbjs": "^0.8.9",
"flatpickr": "^3.0.7",
"graphql": "^0.10.3",
"graphql-date": "^1.0.3",
"graphql-relay": "^0.5.1",
"graphql-sequelize": "^5.4.2",
"graphql-tag": "^1.2.4",
"he": "^1.1.1",
"history": "^4.5.1",
"intl": "^1.2.5",
"intl-locales-supported": "^1.0.0",
"isomorphic-style-loader": "^4.0.0",
"jsonwebtoken": "^7.2.1",
"lodash": "^4.17.4",
"mailchimp-api-v3": "^1.7.1",
"moment": "^2.18.1",
"node-fetch": "^1.6.3",
"node-sass": "^4.5.3",
"normalize.css": "^5.0.0",
"nouislider": "^10.1.0",
"passport": "^0.4.0",
"passport-facebook": "^2.1.1",
"passport-local": "^1.0.0",
"pg": "^6.1.2",
"pretty-error": "^2.0.2",
"prop-types": "^15.6.0",
"query-string": "^4.3.1",
"react": "^16.0.0",
"react-addons-css-transition-group": "^15.6.0",
"react-addons-shallow-compare": "^15.6.0",
"react-addons-transition-group": "^15.6.0",
"react-apollo": "^1.4.3",
"react-bootstrap": "^0.31.0",
"react-calendar": "^1.1.0",
"react-dates": "^12.5.1",
"react-day-picker": "^6.1.0",
"react-dom": "^16.0.0",
"react-google-maps": "^8.5.0",
"react-images": "^0.5.11",
"react-intl": "^2.2.3",
"react-redux": "^5.0.5",
"react-router": "^3.0.2",
"react-select": "^1.0.0-rc.5",
"react-table": "^6.5.3",
"react-toastify": "^2.0.0",
"reactstrap": "^5.0.0-alpha.3",
"recompose": "^0.25.0",
"redux": "^3.7.1",
"redux-form": "^7.0.3",
"redux-logger": "^3.0.6",
"redux-thunk": "^2.2.0",
"sanitize-html": "^1.15.0",
"sass-loader": "^6.0.6",
"sequelize": "^4.13.2",
"sequelize-cli": "^2.5.1",
"sequelize-slugify": "^0.5.0",
"serialize-javascript": "^1.3.0",
"source-map-support": "^0.4.11",
"umzug": "^2.0.1",
"universal-router": "^5.0.0",
"whatwg-fetch": "^2.0.2"
},
"devDependencies": {
"assets-webpack-plugin": "^3.5.1",
"autoprefixer": "^6.7.2",
"babel-cli": "^6.22.2",
"babel-core": "^6.22.1",
"babel-eslint": "^7.1.1",
"babel-loader": "^6.2.10",
"babel-plugin-react-intl": "^2.3.1",
"babel-plugin-rewire": "^1.0.0",
"babel-preset-env": "^1.1.8",
"babel-preset-react": "^6.22.0",
"babel-preset-react-optimize": "^1.0.1",
"babel-preset-stage-2": "^6.22.0",
"babel-register": "^6.22.0",
"babel-template": "^6.22.0",
"babel-types": "^6.22.0",
"browser-sync": "^2.18.7",
"chai": "^3.5.0",
"chokidar": "^1.6.1",
"css-loader": "^0.26.1",
"editorconfig-tools": "^0.1.1",
"enzyme": "^2.7.1",
"eslint": "^3.15.0",
"eslint-config-airbnb": "^14.1.0",
"eslint-loader": "^1.6.1",
"eslint-plugin-css-modules": "^2.2.0",
"eslint-plugin-import": "^2.2.0",
"eslint-plugin-jsx-a11y": "^4.0.0",
"eslint-plugin-react": "^6.9.0",
"file-loader": "^0.10.0",
"front-matter": "^2.1.2",
"glob": "^7.1.1",
"json-loader": "^0.5.4",
"lint-staged": "^3.3.0",
"markdown-it": "^8.2.2",
"mkdirp": "^0.5.1",
"mocha": "^3.2.0",
"pixrem": "^3.0.2",
"pleeease-filters": "^3.0.0",
"postcss": "^5.2.12",
"postcss-calc": "^5.3.1",
"postcss-color-function": "^3.0.0",
"postcss-custom-media": "^5.0.1",
"postcss-custom-properties": "^5.0.2",
"postcss-custom-selectors": "^3.0.0",
"postcss-flexbugs-fixes": "^2.1.0",
"postcss-loader": "^1.2.2",
"postcss-media-minmax": "^2.1.2",
"postcss-nested": "^1.0.0",
"postcss-nesting": "^2.3.1",
"postcss-partial-import": "^3.1.0",
"postcss-pseudoelements": "^3.0.0",
"postcss-selector-matches": "^2.0.5",
"postcss-selector-not": "^2.0.0",
"postcss-url": "^5.1.2",
"pre-commit": "^1.2.2",
"raw-loader": "^0.5.1",
"react-addons-test-utils": "^15.4.2",
"react-deep-force-update": "^2.0.1",
"react-hot-loader": "^3.0.0-beta.6",
"redbox-react": "^1.3.3",
"redux-mock-store": "^1.2.1",
"rimraf": "^2.5.4",
"sinon": "^2.0.0-pre.5",
"stylefmt": "^5.1.1",
"stylelint": "^7.8.0",
"stylelint-config-standard": "^16.0.0",
"url-loader": "^0.5.7",
"webpack": "^2.2.1",
"webpack-bundle-analyzer": "^2.3.0",
"webpack-dev-middleware": "^1.10.0",
"webpack-hot-middleware": "^2.16.1",
"write-file-webpack-plugin": "^3.4.2"
},
"babel": {
"presets": [
[
"env",
{
"targets": {
"node": "current"
}
}
],
"stage-2",
"react"
],
"env": {
"test": {
"plugins": [
"rewire"
]
}
}
},
"eslintConfig": {
"parser": "babel-eslint",
"extends": [
"airbnb",
"plugin:css-modules/recommended"
],
"plugins": [
"css-modules"
],
"globals": {
"__DEV__": true
},
"env": {
"browser": true
},
"rules": {
"import/extensions": "off",
"import/no-extraneous-dependencies": "off",
"no-plusplus": [
"error",
{
"allowForLoopAfterthoughts": true
}
],
"react/jsx-filename-extension": "off",
"react/prefer-stateless-function": "off",
"react/prop-types": "off"
}
},
"stylelint": {
"extends": "stylelint-config-standard",
"rules": {
"string-quotes": "single",
"property-no-unknown": [
true,
{
"ignoreProperties": [
"composes"
]
}
],
"selector-pseudo-class-no-unknown": [
true,
{
"ignorePseudoClasses": [
"global"
]
}
]
}
},
"pre-commit": "lint:staged",
"lint-staged": {
"*.{cmd,html,json,md,sh,txt,xml,yml}": [
"editorconfig-tools fix",
"git add"
],
"*.{js,jsx}": [
"eslint --fix",
"git add"
],
"*.{css,less,sss}": [
"stylefmt",
"stylelint",
"git add"
]
},
"scripts": {
"lint:js": "eslint src tools",
"lint:css": "stylelint \"src/**/*.{css,less,sss}\"",
"lint:staged": "lint-staged || true",
"lint": "yarn run lint:js && yarn run lint:css",
"test": "mocha \"src/**/*.test.js\" --require babel-register --require test/setup.js",
"test:watch": "yarn run test -- --reporter min --watch",
"clean": "babel-node tools/run clean",
"copy": "babel-node tools/run copy",
"extractMessages": "babel-node tools/run extractMessages",
"bundle": "babel-node tools/run bundle",
"build": "babel-node tools/run build",
"build:stats": "yarn run build -- --release --analyse",
"deploy": "babel-node tools/run deploy",
"render": "babel-node tools/run render",
"serve": "babel-node tools/run runServer",
"start": "yarn run migrate && babel-node tools/run start",
"migrate": "sequelize db:migrate",
"seed": "sequelize db:seed:all",
"seed:undo": "sequelize db:seed:undo:all",
}
}
webpack.config.js
/**
* React Starter Kit (https://www.reactstarterkit.com/)
*
* Copyright © 2014-present Kriasoft, LLC. All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE.txt file in the root directory of this source tree.
*/
import path from 'path';
import webpack from 'webpack';
import AssetsPlugin from 'assets-webpack-plugin';
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
import pkg from '../package.json';
const isDebug = !process.argv.includes('--release');
const isVerbose = process.argv.includes('--verbose');
const isAnalyse = process.argv.includes('--analyse') || process.argv.includes('--analyze');
const port = parseInt(process.env.PORT || '3000', 10);
const analyzerPort = port + 3;
// Can be `server`, `static` or `disabled`.
// In `server` mode analyzer will start HTTP server to show bundle report.
// In `static` mode single HTML file with bundle report will be generated.
// In `disabled` mode you can use this plugin to just generate Webpack Stats JSON
// file by setting `generateStatsFile` to `true`.
let analyzerMode = 'disabled';
if (isAnalyse) {
analyzerMode = 'server';
} else if (!isDebug) {
analyzerMode = 'static';
}
//
// Common configuration chunk to be used for both
// client-side (client.js) and server-side (server.js) bundles
// -----------------------------------------------------------------------------
const config = {
context: path.resolve(__dirname, '../src'),
output: {
path: path.resolve(__dirname, '../build/public/assets'),
publicPath: '/assets/',
pathinfo: isVerbose,
},
module: {
rules: [
{
test: /\.jsx?$/,
loader: 'babel-loader',
include: [
path.resolve(__dirname, '../src'),
],
query: {
// https://github.com/babel/babel-loader#options
cacheDirectory: isDebug,
// https://babeljs.io/docs/usage/options/
babelrc: false,
presets: [
// A Babel preset that can automatically determine the Babel plugins and polyfills
// https://github.com/babel/babel-preset-env
['env', {
targets: {
browsers: pkg.browserslist,
},
modules: false,
useBuiltIns: false,
debug: false,
}],
// Experimental ECMAScript proposals
// https://babeljs.io/docs/plugins/#presets-stage-x-experimental-presets-
'stage-2',
// JSX, Flow
// https://github.com/babel/babel/tree/master/packages/babel-preset-react
'react',
// Optimize React code for the production build
// https://github.com/thejameskyle/babel-react-optimize
...isDebug ? [] : ['react-optimize'],
],
plugins: [
// Adds component stack to warning messages
// https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-react-jsx-source
...isDebug ? ['transform-react-jsx-source'] : [],
// Adds __self attribute to JSX which React will use for some warnings
// https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-react-jsx-self
...isDebug ? ['transform-react-jsx-self'] : [],
'react-intl',
],
},
},
{
test: /^((?!\.local).)*\.scss$/,
use: [
{
loader: 'isomorphic-style-loader',
},
{
loader: 'css-loader',
options: {
// CSS Loader https://github.com/webpack/css-loader
importLoaders: 1,
sourceMap: isDebug,
// CSS Modules https://github.com/css-modules/css-modules
modules: true,
localIdentName: isDebug ? '[name]-[local]-[hash:base64:5]' : '[hash:base64:5]',
// CSS Nano http://cssnano.co/options/
minimize: !isDebug,
discardComments: { removeAll: true },
},
},
{
loader: 'postcss-loader',
options: {
config: './tools/postcss.config.js',
},
},
{
loader: 'sass-loader',
},
],
},
{
test: /\.local.scss$/,
use: [
{
loader: 'isomorphic-style-loader',
},
{
loader: 'css-loader',
options: {
// CSS Loader https://github.com/webpack/css-loader
importLoaders: 1,
sourceMap: isDebug,
// CSS Modules https://github.com/css-modules/css-modules
modules: true,
localIdentName: '[local]',
// CSS Nano http://cssnano.co/options/
minimize: !isDebug,
discardComments: { removeAll: true },
},
},
{
loader: 'postcss-loader',
options: {
config: './tools/postcss.config.js',
},
},
{
loader: 'sass-loader',
},
],
},
{
test: /\.css/,
use: [
{
loader: 'isomorphic-style-loader',
},
{
loader: 'css-loader',
options: {
// CSS Loader https://github.com/webpack/css-loader
importLoaders: 1,
sourceMap: isDebug,
// CSS Modules https://github.com/css-modules/css-modules
modules: true,
localIdentName: isDebug ? '[name]-[local]-[hash:base64:5]' : '[hash:base64:5]',
// CSS Nano http://cssnano.co/options/
minimize: !isDebug,
discardComments: { removeAll: true },
},
},
{
loader: 'postcss-loader',
options: {
config: './tools/postcss.config.js',
},
},
],
},
{
test: /\.md$/,
loader: path.resolve(__dirname, './lib/markdown-loader.js'),
},
{
test: /\.txt$/,
loader: 'raw-loader',
},
{
test: /\.(ico|jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2)(\?.*)?$/,
loader: 'file-loader',
query: {
name: isDebug ? '[path][name].[ext]?[hash:8]' : '[hash:8].[ext]',
},
},
{
test: /\.(mp4|webm|wav|mp3|m4a|aac|oga)(\?.*)?$/,
loader: 'url-loader',
query: {
name: isDebug ? '[path][name].[ext]?[hash:8]' : '[hash:8].[ext]',
limit: 10000,
},
},
{
test: /\.(graphql|gql)$/,
exclude: /node_modules/,
loader: 'graphql-tag/loader',
},
],
},
resolve: {
modules: [path.resolve(__dirname, '../src'), 'node_modules'],
},
// Don't attempt to continue if there are any errors.
bail: !isDebug,
cache: isDebug,
stats: {
colors: true,
reasons: isDebug,
hash: isVerbose,
version: isVerbose,
timings: true,
chunks: isVerbose,
chunkModules: isVerbose,
cached: isVerbose,
cachedAssets: isVerbose,
},
};
//
// Configuration for the client-side bundle (client.js)
// -----------------------------------------------------------------------------
const clientConfig = {
...config,
name: 'client',
target: 'web',
entry: {
client: ['babel-polyfill', './clientLoader.js'],
},
output: {
...config.output,
filename: isDebug ? '[name].js' : '[name].[chunkhash:8].js',
chunkFilename: isDebug ? '[name].chunk.js' : '[name].[chunkhash:8].chunk.js',
},
resolve: { ...config.resolve },
plugins: [
// Define free variables
// https://webpack.github.io/docs/list-of-plugins.html#defineplugin
new webpack.DefinePlugin({
'process.env.NODE_ENV': isDebug ? '"development"' : '"production"',
'process.env.BROWSER': true,
__DEV__: isDebug,
}),
// Emit a file with assets paths
// https://github.com/sporto/assets-webpack-plugin#options
new AssetsPlugin({
path: path.resolve(__dirname, '../build'),
filename: 'assets.json',
prettyPrint: true,
}),
// Move modules that occur in multiple entry chunks to a new entry chunk (the commons chunk).
// http://webpack.github.io/docs/list-of-plugins.html#commonschunkplugin
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: module => /node_modules/.test(module.resource),
}),
...isDebug ? [] : [
// Minimize all JavaScript output of chunks
// https://github.com/mishoo/UglifyJS2#compressor-options
new webpack.optimize.UglifyJsPlugin({
sourceMap: true,
compress: {
screw_ie8: true, // React doesn't support IE8
warnings: isVerbose,
unused: true,
dead_code: true,
},
mangle: {
screw_ie8: true,
},
output: {
comments: false,
screw_ie8: true,
},
}),
],
new BundleAnalyzerPlugin({
// See above
analyzerMode,
// Host that will be used in `server` mode to start HTTP server.
analyzerHost: '127.0.0.1',
// Port that will be used in `server` mode to start HTTP server.
analyzerPort,
// Path to bundle report file that will be generated in `static` mode.
// Relative to bundles output directory.
reportFilename: path.resolve(__dirname, '../report.html'),
// Automatically open report in default browser
openAnalyzer: true,
// If `true`, Webpack Stats JSON file will be generated in bundles output directory
generateStatsFile: !isDebug,
// Name of Webpack Stats JSON file that will be generated if `generateStatsFile` is `true`.
// Relative to bundles output directory.
statsFilename: path.resolve(__dirname, '../stats.json'),
// Options for `stats.toJson()` method.
// You can exclude sources of your modules from stats file with `source: false` option.
// See more options here: https://github.com/webpack/webpack/blob/webpack-1/lib/Stats.js#L21
statsOptions: null,
// Log level. Can be 'info', 'warn', 'error' or 'silent'.
logLevel: 'info',
}),
],
// Choose a developer tool to enhance debugging
// http://webpack.github.io/docs/configuration.html#devtool
devtool: isDebug ? 'cheap-module-source-map' : false,
// Some libraries import Node modules but don't use them in the browser.
// Tell Webpack to provide empty mocks for them so importing them works.
// https://webpack.github.io/docs/configuration.html#node
// https://github.com/webpack/node-libs-browser/tree/master/mock
node: {
fs: 'empty',
net: 'empty',
tls: 'empty',
},
};
//
// Configuration for the server-side bundle (server.js)
// -----------------------------------------------------------------------------
const serverConfig = {
...config,
name: 'server',
target: 'node',
entry: {
server: ['babel-polyfill', './server.js'],
},
output: {
...config.output,
filename: '../../server.js',
libraryTarget: 'commonjs2',
},
module: {
...config.module,
// Override babel-preset-env configuration for Node.js
rules: config.module.rules.map(rule => (rule.loader !== 'babel-loader' ? rule : {
...rule,
query: {
...rule.query,
presets: rule.query.presets.map(preset => (preset[0] !== 'env' ? preset : ['env', {
targets: {
node: parseFloat(pkg.engines.node.replace(/^\D+/g, '')),
},
modules: false,
useBuiltIns: false,
debug: false,
}])),
},
})),
},
resolve: { ...config.resolve },
externals: [
/^\.\/assets\.json$/,
(context, request, callback) => {
const isExternal =
request.match(/^[@a-z][a-z/.\-0-9]*$/i) &&
!request.match(/\.(css|less|scss|sss)$/i);
callback(null, Boolean(isExternal));
},
],
plugins: [
// Define free variables
// https://webpack.github.io/docs/list-of-plugins.html#defineplugin
new webpack.DefinePlugin({
'process.env.NODE_ENV': isDebug ? '"development"' : '"production"',
'process.env.BROWSER': false,
__DEV__: isDebug,
}),
// Do not create separate chunks of the server bundle
// https://webpack.github.io/docs/list-of-plugins.html#limitchunkcountplugin
new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1 }),
// Adds a banner to the top of each generated chunk
// https://webpack.github.io/docs/list-of-plugins.html#bannerplugin
new webpack.BannerPlugin({
banner: 'require("source-map-support").install();',
raw: true,
entryOnly: false,
}),
],
node: {
console: false,
global: false,
process: false,
Buffer: false,
__filename: false,
__dirname: false,
},
devtool: isDebug ? 'cheap-module-source-map' : 'source-map',
};
export default [clientConfig, serverConfig];