技本功丨create-react-app升級webpack4填坑

1547091888(1).jpg


都說create-react-app是業界最優秀的 React 應用開發工具之一。But,webpack4都更新到v4.20.2 它居然~還沒升級,簡直~不能忍


a59877c747169b933f9aeab900724e7e.jpg


看到webpack這更新速度,本人慌得一批,剛好抽空搭建react-andt-mobx腳手架準備升級~

So,在此分享一下升級攻略,收好不謝!


d063fb499b64e2e372bd4e1a4c71c3d4.jpg

558196e4a577e6399cfc226d8aacad45.png


01 安裝

npm install -g create-react-app


02 創建應用

//create-react-app是全局命令來創建react項目
create-react-app react-demo


03 自定義webpack配置

npm run eject  //自定義模式,暴露出webpack配置,不可逆


04 着手自定義webpack配置


1、目標結構

3f7b889f936dbec718616a9c80154736.jpg


當然webpack升級準備,調整create-react-app的目錄結構已符合我們項目開發的規範是必不可少的。這裏重點需關注的爲build目錄下的一下文件:


a5c632154d9b2f857bf7aa2cf1315318.jpg

paths文件更改打包路經更改:


ec4dffea2a31f76ec348d5fa6ee0d875.jpg


在項目開發的過程中host配置以及proxy代理是常見的配置,在create-react-app中配置在package.json配置下,靈活性相對不太好,提取webpack中server.js配置:


964df8c4d76c937f96767309420423cf.jpg


別忘了修改webpackDevServer.config.js下引用host及proxy下的引用哦。


此時,目錄改造全部完畢

漸入佳境,趕緊進入正題

399e2b24964b02da5b40d831117fb0f5.jpg


2、webpack3升級webpack4

webpack4新出了一個mode模式,有三種選擇,none,development,production.最直觀的感受就是你可以少些很多配置,因爲一旦你開啓了mode模式,webpack4就會給你設置很多基本的東西。

development模式下,將側重於功能調試和優化開發體驗,包含如下內容:

  • 瀏覽器調試工具

  • 開發階段的詳細錯誤日誌和提示

  • 快速和優化的增量構建機制

production模式下,將側重於模塊體積優化和線上部署,包含如下內容:

  • 開啓所有的優化代碼

  • 更小的bundle大小

  • 去除掉只在開發階段運行的代碼

  • Scope hoisting和Tree-shaking

  • 自動啓用uglifyjs對代碼進行壓縮

話不多說,下安裝:
yarn add webpack webpack-cli webpack-dev-server

  • 這3個包是webpack4的基礎功能

  • webpack 在 webpack 4 裏將命令行相關的都遷移至 webpack-cli 包

  • webpack-dev-server爲實時監控文件變化包

安裝完成之後,請保持淡定,

得先運行一下,萬一直接能打包呢

或許它偷偷做了兼容處理呢,

夢想還是要有呢,雖然...

6109deeb7d1e1360439d56d064794ee4.jpg

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'.


果然還是熟悉的味道

上面這個問題是HtmlWebpackPlugin 和 react-dev-utils/InterpolateHtmlPlugin 先後順序問題,調整下他們的順序

  new HtmlWebpackPlugin({
      inject: true,
      template: paths.appHtml,
      chunksSortMode: 'none',
    }),
    new InterpolateHtmlPlugin(env.raw),

嗯,不出意外的話,搞定

再跑一下代碼,這個時候可能就出現了一些百度不到問題,你需要升級各種loader了,

less-loader,sass-loader style-loader url-loader

具體命令:

yarn add less-loader@next

和上面的命令相同,依次升級,運行代碼,查看報錯,缺啥補啥,成功的選擇,值得擁有....

需要升級的有:

  • html-webpack-plugin

  • react-dev-utils

修改代碼完整篇 webpack.config.dev.js:

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 getClientEnvironment = require('./env'); const paths = require('./paths'); 
function resolve (dir) {  return path.join(__dirname, '..', dir) } 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','.less','.scss'],    
alias: {      
'@': resolve('src'),      
'public': resolve('src/public'),      
'components': resolve('src/components'),      
'pages': resolve('src/pages'),      
'api': resolve('src/api'),      
'mock': resolve('src/public/mock'),    },    
plugins: [      new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]),    
],  
},  
module: {    
strictExportPresence: true,    
rules: [      
{        
test: /\.(js|jsx|mjs)$/,        
enforce: 'pre',        
use: [          
{            
options: {              
formatter: eslintFormatter,              
eslintPath: require.resolve('eslint'),                          
},            
loader: require.resolve('eslint-loader'),          
},       
],        
include: paths.appSrc,      
},      
{        
oneOf: [          
{            
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],            
loader: require.resolve('url-loader'),            
options: {              
limit: 10000,              
name: 'static/media/[name].[hash:8].[ext]',            
},          
},          
{            
test: /\.(js|jsx|mjs)$/,            
include: paths.appSrc,            
loader: require.resolve('babel-loader'),            
options: {            
cacheDirectory: true,          
}          
},          
{            
test: /\.(css|less)$/,            
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', // React doesn't support IE8 anyway                      
],                      
flexbox: 'no-2009',                    
}),                  
],                
},              
},              
{                
loader: require.resolve('less-loader') // compiles Less to CSS              
},            
],          
},          
{            
test: /\.(css|scss)$/,            
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', // React doesn't support IE8 anyway                      
],                      
flexbox: 'no-2009',                    
}),                  
],                
},             
},              
{                
loader: require.resolve('sass-loader') // compiles Less to CSS              
},            
],          
},          
{            
exclude: [/\.(js|jsx|mjs)$/,/\.(css|less)$/, /\.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',  
}, 
};


還需注意的是webpack4對ExtractTextWebpackPlugin做了調整,建議選用新的CSS文件提取插件mini-css-extract-plugin。生產環境下我們需要做一下配置調整:

webpack.config.prod.js


const autoprefixer = require('autoprefixer');
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ManifestPlugin = require('webpack-manifest-plugin');
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin');
const eslintFormatter = require('react-dev-utils/eslintFormatter');
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const paths = require('./paths');
const getClientEnvironment = require('./env');
const theme = require('../antd-theme.js');
function resolve (dir) {
  return path.join(__dirname, '..', dir)
}
const publicPath = paths.servedPath;
const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false';
const publicUrl = publicPath.slice(0, -1);
const env = getClientEnvironment(publicUrl);

if (env.stringified['process.env'].NODE_ENV !== '"production"') {
  throw new Error('Production builds must have NODE_ENV=production.');
}

module.exports = {
  mode: "production",
  bail: true,
  devtool: shouldUseSourceMap ? 'source-map' : false,
  entry: [require.resolve('./polyfills'), paths.appIndexJs],
  output: {
    path: paths.appBuild,
    filename: 'static/js/[name].[chunkhash:8].js',
    chunkFilename: 'static/js/[name].[chunkhash:8].chunk.js',
    publicPath: publicPath,
    devtoolModuleFilenameTemplate: info =>
      path
        .relative(paths.appSrc, info.absoluteResourcePath)
        .replace(/\\/g, '/'),
  },
  resolve: {
    modules: ['node_modules', paths.appNodeModules].concat(
      // It is guaranteed to exist because we tweak it in `env.js`
      process.env.NODE_PATH.split(path.delimiter).filter(Boolean)
    ),
    extensions: ['.web.js', '.mjs', '.js', '.json', '.web.jsx', '.jsx','.less'],
    alias: {
      '@': resolve('src'),
      'public': resolve('src/public'),
      'components': resolve('src/components'),
      'pages': resolve('src/pages'),
      'mock': resolve('src/public/mock'),
      'api': resolve('src/api'),
      'react-native': 'react-native-web',
    },
    plugins: [
      new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]),
    ],
  },
  module: {
    strictExportPresence: true,
    rules: [
      {
        test: /\.(js|jsx|mjs)$/,
        enforce: 'pre',
        use: [
          {
            options: {
              formatter: eslintFormatter,
              eslintPath: require.resolve('eslint'),
              
            },
            loader: require.resolve('eslint-loader'),
          },
        ],
        include: paths.appSrc,
      },
      {
        oneOf: [
          {
            test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
            loader: require.resolve('url-loader'),
            options: {
              limit: 10000,
              name: 'static/media/[name].[hash:8].[ext]',
            },
          },
          // Process JS with Babel.
          {
            test: /\.(js|jsx|mjs)$/,
            include: paths.appSrc,
            loader: require.resolve('babel-loader'),
            options: {
              plugins: [
                  ['import', [{ libraryName: 'antd', style: true }]],  // import less
              ],
              compact: true,
            },
          },
          {
            test: /\.(less|css)$/,
            use: [
              MiniCssExtractPlugin.loader,
              "css-loader",
              "less-loader?{modifyVars:" + JSON.stringify(theme) + "}"
            ],
          },
          {
            test: /\.(scss|sass)$/,
            use: [
              MiniCssExtractPlugin.loader,
              "css-loader",
              "sass-loader"
            ]
          },
          {
            loader: require.resolve('file-loader'),
            exclude: [/\.(js|jsx|mjs)$/,/\.(css|less)$/, /\.html$/, /\.json$/],
            options: {
              name: 'static/media/[name].[hash:8].[ext]',
            },
          },
        ],
      },
    ],
  },
  optimization: {
    runtimeChunk: {
      name: 'manifest'
    },
    minimize: true,
    noEmitOnErrors: true,
    minimizer: [
      new UglifyJsPlugin({
        cache: true,
        parallel: true,
        sourceMap: false
      }),
      new OptimizeCSSAssetsPlugin({})
    ],
    splitChunks: {
      minSize: 30000,
      maxSize: 3000000,
      minChunks: 1,
      maxAsyncRequests: 5,
      maxInitialRequests: 3,
      name: true,
      cacheGroups: {
        vendor: {
          chunks: 'initial',
          name: 'vendor',
          test: 'vendor'
        },
        echarts: {
          chunks: 'all',
          name: 'echarts',
          test: /[\\/]echarts[\\/]/,
        }
      }
    }
  },
  plugins: [
    new HtmlWebpackPlugin({
      inject: true,
      template: paths.appHtml,
      minify: {
        removeComments: true,
        collapseWhitespace: true,
        removeRedundantAttributes: true,
        useShortDoctype: true,
        removeEmptyAttributes: true,
        removeStyleLinkTypeAttributes: true,
        keepClosingSlash: true,
        minifyJS: true,
        minifyCSS: true,
        minifyURLs: true,
      },
    }),
    new InterpolateHtmlPlugin(env.raw),
    new webpack.DefinePlugin(env.stringified),
    new webpack.NamedModulesPlugin(),
    new webpack.optimize.OccurrenceOrderPlugin(true),
    new MiniCssExtractPlugin({
      filename: "css/[name].[hash].css",
      chunkFilename: "css/[name].[hash].css"
    }),
    new ManifestPlugin({
      fileName: 'asset-manifest.json',
    }),
    new SWPrecacheWebpackPlugin({
      dontCacheBustUrlsMatching: /\.\w{8}\./,
      filename: 'service-worker.js',
      logger(message) {
        if (message.indexOf('Total precache size is') === 0) {
          return;
        }
        if (message.indexOf('Skipping static resource') === 0) {
          return;
        }
        console.log(message);
      },
      minify: true,
      navigateFallback: publicUrl + '/index.html',
      navigateFallbackWhitelist: [/^(?!\/__).*/],
      staticFileGlobsIgnorePatterns: [/\.map$/, /asset-manifest\.json$/],
    }),
    new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
  ],
  node: {
    dgram: 'empty',
    fs: 'empty',
    net: 'empty',
    tls: 'empty',
    child_process: 'empty',
  },
};


此時基本完成了webpack4升級的改造

但是運行下,還有個隱形的坑待處理……

因爲引入了andt組件庫,

高版本對less編譯會出錯。。

噗~升級了一路,碰到個需要降級處理的,

好,降低less版本 "less": "2.7.3",

運行一波,親測完美~

b59d4e2bbcbb68f44adb1e16ba319a13.jpg


最後附package.json

{
  "dependencies": {
    "antd": "^3.9.0-beta.6",
    "autoprefixer": "7.1.6",
    "axios": "^0.18.0",
    "babel-core": "6.26.0",
    "babel-eslint": "7.2.3",
    "babel-jest": "20.0.3",
    "babel-loader": "7.1.2",
    "babel-plugin-import": "^1.8.0",
    "babel-polyfill": "^6.26.0",
    "babel-preset-react-app": "^3.1.1",
    "babel-runtime": "^6.26.0",
    "case-sensitive-paths-webpack-plugin": "2.1.1",
    "chalk": "1.1.3",
    "classnames": "^2.2.6",
    "core-decorators": "^0.20.0",
    "create-keyframe-animation": "^0.1.0",
    "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": "^2.1.1",
    "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",
    "express": "^4.16.3",
    "fastclick": "^1.0.6",
    "file-loader": "2.0.0",
    "fs-extra": "3.0.1",
    "good-storage": "^1.0.1",
    "history": "^4.7.2",
    "html-webpack-plugin": "^3.2.0",
    "immutable": "^3.8.2",
    "jest": "20.0.4",
    "js-base64": "^2.4.3",
    "jsonp": "^0.2.1",
    "less": "2.7.3",
    "less-loader": "^4.0.1",
    "lyric-parser": "^1.0.1",
    "mini-css-extract-plugin": "^0.4.3",
    "mobx": "^4.1.1",
    "mobx-react": "^5.0.0",
    "mobx-react-devtools": "^5.0.1",
    "node-sass": "^4.9.3",
    "object-assign": "4.1.1",
    "optimize-css-assets-webpack-plugin": "^5.0.1",
    "postcss-flexbugs-fixes": "3.2.0",
    "postcss-loader": "2.0.8",
    "promise": "8.0.1",
    "prop-types": "^15.6.1",
    "raf": "3.4.0",
    "react": "^16.3.0",
    "react-addons-css-transition-group": "^15.6.2",
    "react-dev-utils": "^6.0.0-next.a671462c",
    "react-dom": "^16.3.0",
    "react-hot-loader": "^4.3.4",
    "react-lazyload": "^2.3.0",
    "react-loadable": "^5.5.0",
    "react-router-dom": "^4.2.2",
    "react-transition-group": "^2.3.1",
    "sass-loader": "^7.1.0",
    "style-loader": "0.19.0",
    "sw-precache-webpack-plugin": "^0.11.5",
    "uglifyjs-webpack-plugin": "^2.0.1",
    "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": {
    "start": "node scripts/start.js",
    "build": "node scripts/build.js"
  },
  "babel": {
    "presets": [
      "react-app"
    ],
    "plugins": [
      "transform-decorators-legacy"
    ]
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "devDependencies": {
    "babel-plugin-transform-decorators-legacy": "^1.3.4",
    "better-scroll": "^1.9.1"
  },
}


結 語

前端的框架更新速度,悄無聲息又超乎想象,需要不斷保持着對前端的熱情和主動,研究一波前沿的技術架構和設計理念....

比如參加D2前端技術沙龍~

恍惚中,感覺這一波操作

好像還能再優化~優化~


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章