从 roadhog 转移到 create-react-app 并升级 webpack4
项目环境说明
公司项目,使用 antd 做为开发的 UI 框架, 项目使用预编译语言 less,这主要是为了和 antd 官方保持一致,
项目中有一些 tsx 的组件和 一些 ts 的脚本,所以 新的配置必须能够适应 js 和 ts 混合编译,支持
css-modules, 支持 less 以及 antd 的动态导入
完整开发配置
eject 配置
$ yarn eject # npm run eject
开启自定义配置
webpack.config.dev.js
其中纯 CRA 配置不存在自定义,不过把原有 CRA 的配置都升级到 webpack4 的版本上了。其中包含了 webpack
的配置特点,以及如何解决 css-modules 和 antd 的动态样式导入的冲突
// webpack.config.dev.js
'use strict'
const autoprefixer = require('autoprefixer')
const path = require('path')
const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin')
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin')
const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin')
const eslintFormatter = require('react-dev-utils/eslintFormatter')
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin')
const tsImportPluginFactory = require('ts-import-plugin')
const getClientEnvironment = require('./env')
const paths = require('./paths')
const publicPath = '/'
const publicUrl = ''
const env = getClientEnvironment(publicUrl)
module.exports = {
mode: 'development',
devtool: 'cheap-module-source-map',
entry: [
require.resolve('./polyfills'),
require.resolve('react-dev-utils/webpackHotDevClient'),
paths.appIndexJs,
],
output: {
pathinfo: true,
filename: 'static/js/bundle.js',
chunkFilename: 'static/js/[name].chunk.js',
publicPath: publicPath,
devtoolModuleFilenameTemplate: (info) =>
path.resolve(info.absoluteResourcePath).replace(/\\/g, '/'),
},
resolve: {
modules: ['node_modules', paths.appNodeModules].concat(
process.env.NODE_PATH.split(path.delimiter).filter(Boolean)
),
extensions: ['.web.js', '.mjs', '.js', '.json', '.web.jsx', '.jsx', '.ts', '.tsx'],
alias: {
'react-native': 'react-native-web',
},
plugins: [new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson])],
},
module: {
strictExportPresence: true,
rules: [
{
oneOf: [
{
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
loader: require.resolve('url-loader'),
options: {
limit: 10000,
name: 'static/media/[name].[hash:8].[ext]',
},
},
{
test: /\.(ts|tsx)$/,
use: [
{
loader: require.resolve('awesome-typescript-loader'),
options: {
transpileOnly: true,
useCache: true,
useBabel: true,
babelOptions: {
babelrc: true,
},
babelCore: '@babel/core',
},
},
],
exclude: /node_modules/,
},
{
test: /\.(js|jsx|mjs)$/,
include: paths.appSrc,
// loader: 'happypack/loader',
loader: require.resolve('babel-loader'),
query: {
cacheDirectory: true,
// plugins: [['import', { libraryName: 'antd', style: true }]],
},
},
{
test: /\.css$/,
use: [
require.resolve('style-loader'),
{
loader: require.resolve('css-loader'),
options: {
importLoaders: 1,
modules: true,
localIdentName: '[name]__[local]___[hash:base64:5]',
},
},
{
loader: require.resolve('postcss-loader'),
options: {
ident: 'postcss',
plugins: () => [
require('postcss-flexbugs-fixes'),
autoprefixer({
browsers: ['>1%', 'last 4 versions', 'Firefox ESR', 'not ie < 9'],
flexbox: 'no-2009',
}),
],
},
},
],
},
{
test: /\.less$/,
exclude: path.resolve(__dirname, '../node_modules'),
use: [
require.resolve('style-loader'),
{
loader: require.resolve('css-loader'),
options: {
importLoaders: 1,
modules: true,
localIdentName: '[name]__[local]___[hash:base64:5]',
},
},
{
loader: require.resolve('postcss-loader'),
options: {
ident: 'postcss',
plugins: () => [
require('postcss-flexbugs-fixes'),
autoprefixer({
browsers: ['>1%', 'last 4 versions', 'Firefox ESR', 'not ie < 9'],
flexbox: 'no-2009',
}),
],
},
},
{
loader: require.resolve('less-loader'),
options: {
modifyVars: { '@primary-color': '#1DA57A' },
javascriptEnabled: true,
},
},
],
},
{
test: /\.less$/,
include: [path.resolve(__dirname, '../node_modules/')],
use: [
require.resolve('style-loader'),
{
loader: require.resolve('css-loader'),
options: {
importLoaders: 1,
},
},
{
loader: require.resolve('postcss-loader'),
options: {
ident: 'postcss',
plugins: () => [
require('postcss-flexbugs-fixes'),
autoprefixer({
browsers: ['>1%', 'last 4 versions', 'Firefox ESR', 'not ie < 9'],
flexbox: 'no-2009',
}),
],
},
},
{
loader: require.resolve('less-loader'),
options: {
modifyVars: { '@primary-color': '#1DA57A' },
javascriptEnabled: true,
},
},
],
},
{
exclude: [/\.(js|jsx|mjs)$/, /\.html$/, /\.json$/],
loader: require.resolve('file-loader'),
options: {
name: 'static/media/[name].[hash:8].[ext]',
},
},
],
},
],
},
plugins: [
new HtmlWebpackPlugin({
inject: true,
template: paths.appHtml,
chunksSortMode: 'none',
}),
new InterpolateHtmlPlugin(env.raw),
new webpack.DefinePlugin(env.stringified),
new webpack.HotModuleReplacementPlugin(),
new CaseSensitivePathsPlugin(),
new WatchMissingNodeModulesPlugin(paths.appNodeModules),
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
],
node: {
dgram: 'empty',
fs: 'empty',
net: 'empty',
tls: 'empty',
child_process: 'empty',
},
performance: {
hints: false,
},
optimization: {
namedModules: true,
nodeEnv: 'development',
},
}
在上面配置中,有变动的地方和 HtmlWebpackPlugin 和 react-dev-utils/InterpolateHtmlPlugin 先后顺序,为
了解决下面问题:
Plugin could not be registered at 'html-webpack-plugin-before-html-processing'. Hook was not found.
BREAKING CHANGE:
There need to exist a hook at 'this.hooks'. To create a compatiblity layer for this hook, hook into 'this._pluginCompat'.
其次关于 test: /\.less/
有两次,观察两个 loader 的不同,一个 配置中 css-modules 是关闭,一个是开启
的,同时一个 exclude 是去除 node_modules
一个是 include node_modules
, 为了解决 css-modules 的开
启会导致 antd 的样式无法导入问题, 具体可以关注 ant-design 的仓库 issue, 搜索 样式无法导入
即可。
上面方案灵感来自于 roadhog loaders 配置
package.json
这部分主要包含最近发新版的包,如下:
// package.json
{
"name": "dva项目",
"version": "0.1.0",
"private": true,
"dependencies": {
"@babel/polyfill": "^7.0.0",
"antd": "^3.9.2",
"bizcharts": "^3.2.2",
"classnames": "^2.2.6",
"currency.js": "^1.1.4",
"dva": "^2.4.0",
"enquire-js": "^0.2.1",
"fastclick": "^1.0.6",
"immutable": "^3.8.2",
"lodash": "^4.17.11",
"lodash-decorators": "^6.0.0",
"qrcode": "^1.2.2",
"rc-drawer-menu": "^1.1.0",
"rc-trigger": "^2.5.4",
"react": "^16.5.1",
"react-container-query": "^0.11.0",
"react-countup": "^4.0.0-alpha.6",
"react-dnd": "^5.0.0",
"react-dnd-html5-backend": "^5.0.1",
"react-document-title": "^2.0.3",
"react-dom": "^16.5.1",
"react-virtualized": "^9.20.1",
"rollbar": "^2.4.6",
"ua-parser-js": "^0.7.18",
"xlsx": "^0.14.0"
},
"devDependencies": {
"@babel/core": "^7.0.1",
"@babel/plugin-proposal-class-properties": "^7.0.0",
"@babel/plugin-proposal-decorators": "^7.0.0",
"@babel/plugin-proposal-optional-chaining": "^7.0.0",
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"@babel/preset-react": "^7.0.0",
"@babel/runtime": "^7.0.0",
"@types/lodash": "^4.14.116",
"@types/react": "^16.4.14",
"@types/react-dom": "^16.0.7",
"autoprefixer": "7.1.6",
"awesome-typescript-loader": "^5.2.1",
"babel-eslint": "7.2.3",
"babel-jest": "20.0.3",
"babel-loader": "^8.0.0-beta.6",
"babel-plugin-import": "^1.8.0",
"case-sensitive-paths-webpack-plugin": "2.1.1",
"chalk": "1.1.3",
"css-loader": "0.28.7",
"dotenv": "4.0.0",
"dotenv-expand": "4.2.0",
"eslint": "4.10.0",
"eslint-config-react-app": "^2.1.0",
"eslint-loader": "1.9.0",
"eslint-plugin-flowtype": "2.39.1",
"eslint-plugin-import": "2.8.0",
"eslint-plugin-jsx-a11y": "5.1.1",
"eslint-plugin-react": "7.4.0",
"extract-text-webpack-plugin": "3.0.2",
"file-loader": "^2.0.0",
"fs-extra": "3.0.1",
"happypack": "^5.0.0",
"hard-source-webpack-plugin": "^0.12.0",
"html-webpack-plugin": "^3.2.0",
"jest": "20.0.4",
"less": "^3.8.1",
"less-loader": "^4.1.0",
"object-assign": "4.1.1",
"postcss-flexbugs-fixes": "3.2.0",
"postcss-loader": "2.0.8",
"promise": "8.0.1",
"raf": "3.4.0",
"react-dev-utils": "^6.0.0-next.a671462c",
"resolve": "1.6.0",
"style-loader": "0.19.0",
"sw-precache-webpack-plugin": "^0.11.5",
"ts-import-plugin": "^1.5.5",
"typescript": "^3.0.3",
"url-loader": "0.6.2",
"webpack": "^4.19.0",
"webpack-cli": "^3.1.0",
"webpack-dev-server": "^3.1.8",
"webpack-manifest-plugin": "^2.0.4",
"whatwg-fetch": "2.0.3"
},
"scripts": {
"dev": "NODE_ENV=development webpack --config config/webpack.config.dev.js --profile",
"start": "node scripts/start.js",
"build": "node scripts/build.js",
"test": "node scripts/test.js --env=jsdom"
}
}
.babelrc
不仅将 webpack 从 v3 到 v4 而且,基本把 babel 都升级到最新版,最新版的 babel 都是在 @babel 命名空间
下
{
"presets": ["@babel/preset-env", "@babel/preset-react"],
"plugins": [
["import", { "libraryName": "antd", "libraryDirectory": "es", "style": true }],
"@babel/plugin-syntax-dynamic-import",
["@babel/plugin-proposal-decorators", { "legacy": true }],
"@babel/plugin-proposal-class-properties",
"@babel/plugin-proposal-optional-chaining"
]
}
常见问题
WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/
增加 webpack 文件中 mode: 'production'
或者 development
(node:6005) DeprecationWarning: Tapable.plugin is deprecated. Use new API on `.hooks` instead
at ModuleScopePlugin.apply (/Users/yes/.../node_modules/react-dev-utils/ModuleScopePlugin.js:21:14)
升级 react-dev-utils 即可
$ yarn add -D react-dev-utils@next
总结
使用 cra 以后, 虽然一开始还是比较慢,但是已经比 roadhog 编译快很多了,而且热编译也很不错,最重要是你能够对项目从配置到具体业务都有充足了解和认识,同时也对 roadhog 学习一波. 用上 webpack4 以后又可以对v4 版本的 Code Splitting, chunk graph and the splitChunks optimization
进行学习了
版本来自: https://luoyangfu.com/detail/...