公司项目,使用 antd 作为开发的 UI 框架, 项目使用预编译语言 less,这主要是为了和 antd 官方保持一致,
项目中有一些 tsx 的组件和 一些 ts 的脚本,因此 新的配置必须可以适应 js 和 ts 混合编译,支持
css-modules, 支持 less 以及 antd 的动态导入javascript
$ yarn eject # npm run eject
开启自定义配置css
其中纯 CRA 配置不存在自定义,不过把原有 CRA 的配置都升级到 webpack4 的版本上了。其中包含了 webpack
的配置特色,以及如何解决 css-modules 和 antd 的动态样式导入的冲突html
// 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 前后顺序,为
了解决下面问题:html5
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, 搜索 样式没法导入
便可。java
上面方案灵感来自于 roadhog loaders 配置
这部分主要包含最近发新版的包,以下:node
// 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" } }
不只将 webpack 从 v3 到 v4 并且,基本把 babel 都升级到最新版,最新版的 babel 都是在 @babel 命名空间
下react
{ "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
webpack
(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 便可web
$ yarn add -D react-dev-utils@next
使用 cra 之后, 虽然一开始仍是比较慢,可是已经比 roadhog 编译快不少了,并且热编译也很不错,最重要是你可以对项目从配置到具体业务都有充足了解和认识,同时也对 roadhog 学习一波. 用上 webpack4 之后又能够对v4 版本的 Code Splitting, chunk graph and the splitChunks optimization
进行学习了typescript