Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

webpack4 中的 React 全家桶配置指南,实战! #159

Open
husky-dot opened this issue Dec 5, 2019 · 0 comments
Open

webpack4 中的 React 全家桶配置指南,实战! #159

husky-dot opened this issue Dec 5, 2019 · 0 comments

Comments

@husky-dot
Copy link
Owner

husky-dot commented Dec 5, 2019

最新React全家桶实战使用配置指南

这篇文档 是我在听 吕小明老师的课程,吕老师结合以往的项目经验 加上自己本身对react webpack redux理解写下的总结文档,总共耗时一周总结下来的,希望能对读者能够有收获, 我是在这基础多些加工,希望对你们有所收藏

目录

1.版本说明

2.目录结构

3.初始化项目

4.webpack

5.react

6.配置loader(sass,jsx))

7.引入babel

8.使用HtmlWebpackPlugin

9.redux

10.使用webpack-dev-server

11.多入口页面配置

12.如何理解entry point(bundle),chunk,module

13.多入口页面html配置

14.模块热替换(Hot Module Replacement)

15.使用ESLint

16.使用react-router

17.使用redux-thunk

18.使用axios和async/await

19.Code Splitting

20.使用CommonsChunkPlugin


如果本文对你有所帮助,你需要服务器的可以看看下面的,我可以赚点小费,写文章不易,还望支持。

阿里云双12已开启,新老用户均可参与,2核1G云服务器仅需79元,,更多服务器配置及价格请关注:Hi拼团,或点此了解“云上爆款1折特惠活动”。同时,建议在购买阿里云相关产品前先领取阿里云2000元代金券会更优惠哦。


版本说明

由于构建相关例如webpack,babel等更新的较快,所以本教程以下面各种模块的版本号为主,切勿轻易修改或更新版本。

"dependencies": {
    "babel-core": "^6.26.3",
    "babel-eslint": "^8.2.3",
    "babel-loader": "^7.1.4",
    "babel-plugin-transform-async-to-generator": "^6.24.1",
    "babel-plugin-transform-runtime": "^6.23.0",
    "babel-preset-es2015": "^6.24.1",
    "babel-preset-react": "^6.24.1",
    "babel-preset-stage-0": "^6.24.1",
    "babel-preset-stage-3": "^6.24.1",
    "css-loader": "^0.28.11",
    "eslint": "^4.19.1",
    "eslint-loader": "^2.0.0",
    "eslint-plugin-react": "^7.9.1",
    "file-loader": "^1.1.11",
    "history": "^4.7.2",
    "html-webpack-plugin": "^3.2.0",
    "react": "^16.4.0",
    "react-dom": "^16.4.0",
    "react-hot-loader": "^4.0.0",
    "react-redux": "^5.0.7",
    "react-router-dom": "^4.3.1",
    "react-router-redux": "^5.0.0-alpha.9",
    "redux": "^4.0.0",
    "sass-loader": "^7.0.3",
    "style-loader": "^0.21.0",
    "url-loader": "^1.0.1",
    "webpack": "^4.12.0",
    "webpack-cli": "^3.0.3",
    "webpack-dev-server": "^3.1.1"
}

目录结构

开发和发布版本的配置文件是分开的,多入口页面的目录结构。

react-family/
    |
    |──dist/                                    * 发布版本构建输出路径
    |
    |──dev/                                     * 调试版本构建输出路径
    |
    |──src/                                     * 工具函数
    |     |
    |     |—— component/                        * 各页面公用组件
    |     |
    |     |—— page/                             * 页面代码
    |     |      |—— index/                     * 页面代码
    |     |      |        |—— Main/             * 组件代码
    |     |      |        |       |—— Main.jsx  * 组件jsx
    |     |      |        |       |—— Main.scss * 组件css
    |     |      |
    |     |      |—— detail/                    * 页面代码
    |     |
    |     |—— static/                           * 静态文件js,css
    |
    |
    |──webpack.config.build.js                  * 发布版本使用的webpack配置文件
    |──webpack.config.dev.js                    * 调试版本使用的webpack配置文件
    |──.eslint                                  * eslint配置文件
    |__.babelrc                                 * babel配置文件

初始化项目

1.创建文件夹

mkdir react-family-bucket

2.初始化npm

cd react-family-bucket
npm init

如果有特殊需要,可以填入自己的配置,一路回车下来,会生成一个package.json,里面是你项目的基本信息,后面的npm依赖安装也会配置在这里。

webpack

1.安装webpack

npm install webpack@4.12.0 --save
or
npm install webpack@4.12.0 --g

--save 是将当前webpack安装到react-family-bucket下的/node_modules。

--g 是将当前webpack安装到全局下面,可以在node的安装目录下找到全局的/node_modules。

2.配置webopack配置文件

touch webpack.config.dev.js

3.新建一个app.js

touch app.js

写入基本的webpack配置,可以参考这里

const path = require('path');
const srcRoot = './src';
module.exports = {

    // 输入配置
    entry: [
      './app.js'
    ],,

    // 输出配置
    output: {
        path: path.resolve(__dirname, './dev'),

        filename: 'bundle.min.js'
    },
};

3.执行webpack命令

如果是全局安装:

webpack --config webpack.config.dev.js

如果是当前目录安装:

./node_modules/.bin/webpack --config webpack.config.dev.js

为了方便我们使用,可以在package.json中 scripts 添加执行命令:

"scripts": {
  "dev": "./node_modules/.bin/webpack --config webpack.config.dev.js",
},

执行npm run dev命令之后,会发现需要安装webpack-cli,(webpack4之后需要安装这个)

npm install webpack-cli --save

安装后,执行 npm run dev 会发现控制台有个警告 ** WARNING in configuration** ,去除WARNING in configuration 警告,在webpack.config.dev.js 增加一个配置即可:

...
mode: 'development'
...

成功之后会在dev下面生成bundle.min.js代表正常。

如果想要动态监听文件变化需要在命令后面添加 --watch。

react

1.安装react

npm install react react-dom --save

2.创建page目录和index页面文件:

mkdir src
mkdir page
cd page

3.创建index

mkdir index
cd index & touch index.js & touch index.html

index.js

import ReactDom from 'react-dom';
import Main from './Main/Main.jsx';

ReactDom.render(<Main />, document.getElementById('root'));

index.html

<!DOCTYPE html>
<html>
<head>
    <title>index</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">

</head>
<body>
<div id="root"></div>
</body>
</html>

4.创建Main组件

import React from 'react';

class Main extends React.Component {

    constructor(props) {
        super(props);
    }

    render() {
        return (<div>Main</div>);
    }
}
export default Main;

5.修改webpack配置入口文件

entry: [
    path.resolve(srcRoot,'./page/index/index.js')
],

配置loader

1.处理样式文件需要这些loader:

配置:

module: {
    // 加载器配置
    rules: [
        { test: /\.css$/, use: ['style-loader', 'css-loader'], include: path.resolve(srcRoot)},
        { test: /\.scss$/, use: ['style-loader', 'css-loader', 'sass-loader'], include: path.resolve(srcRoot)}
    ]
},

2.url-loader 处理处理静态文件

npm install url-loader --save

配置:

module: {
    // 加载器配置
    rules: [
        { test: /\.(png|jpg|jpeg)$/, use: 'url-loader?limit=8192&name=images/[name].[hash].[ext]', include: path.resolve(srcRoot)}
    ]
},

limit:表示超过多少就使用base64来代替,单位是byte

name:可以设置图片的路径,名称和是否使用hash 具体参考这里

引入babel

bebel是用来解析es6语法或者是es7语法分解析器,让开发者能够使用新的es语法,同时支持jsx,vue等多种框架。

1.安装babel

配置:

module: {
    // 加载器配置
    rules: [
        { test: /\.(js|jsx)$/, use: [{loader:'babel-loader'}] ,include: path.resolve(srcRoot)},
    ]
},

2.babel配置文件:.babelrc

touch .babelrc

配置:

{
    "presets": [
        "es2015",
        "react",
        "stage-0"
    ],
    "plugins": []
}

babel支持自定义的预设(presets)或插件(plugins),只有配置了这两个才能让babel生效,单独的安装babel是无意义的。

presets:代表babel支持那种语法(就是你用那种语法写),优先级是从下往上,state-0|1|2|..代表有很多没有列入标准的语法回已state-x表示,参考这里

plugins:代表babel解析的时候使用哪些插件,作用和presets类似,优先级是从上往下。

依次安装:

3.babel-polyfill是什么?

我们之前使用的babel,babel-loader 默认只转换新的 JavaScript 语法,而不转换新的 API。例如,Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise 等全局对象,以及一些定义在全局对象上的方法(比如 Object.assign)都不会转译。如果想使用这些新的对象和方法,必须使用 babel-polyfill,为当前环境提供一个垫片。

npm install --save babel-polyfill

4.transform-runtime 有什么区别?

当使用babel-polyfill时有一些问题:

  • 默认会引入所有babel支持的新语法,这样就会导致你的文件代码非常庞大。

  • 通过向全局对象和内置对象的prototype上添加方法来达成目的,造成全局变量污染。

这时就需要transform-runtime来帮我们有选择性的引入:

npm install --save babel-plugin-transform-runtime

配置文件:

{
  "plugins": [
    ["transform-runtime", {
      "helpers": false,
      "polyfill": false,
      "regenerator": true,
      "moduleName": "babel-runtime"
    }]
  ]
}

使用HtmlWebpackPlugin

记得我们之前新建的index.html么 我们执行构建命令之后并没有将index.html打包到dev目录下 我们需要HtmlWebpackPlugin来将我们output的js和html结合起来:

npm install html-webpack-plugin --save

配置:

const HtmlWebpackPlugin = require('html-webpack-plugin');
...
plugins: [
    new HtmlWebpackPlugin({
        filename: path.resolve(devPath, 'index.html'),
        template: path.resolve(srcRoot, './page/index/index.html'),
    })
]

filename:可以设置html输出的路径和文件名
template:可以设置已哪个html文件为模版

更多参数配置可以参考这里

redux

关于redux的使用可以参考阮一峰老师的入门教程

1.安装redux

1.新建reducers,actions目录和文件

|—— index/                          
|—— Main/                   * 组件代码
|       |—— Main.jsx        * 组件jsx
|       |—— Main.scss       * 组件css
|
|—— actions/ 
|       |—— actionTypes.js  * action常量
|       |—— todoAction.js   * action
|
|—— reducers/ 
|       |—— todoReducer.js  * reducer
|
|—— store.js
|
|—— index.js

2.修改代码,引入redux,这里以一个redux todo为demo例子:

index.js

import ReactDom from 'react-dom';
import React from 'react';
import Main from './Main/Main.jsx';
import store from './store.js';
import { Provider } from 'react-redux';

ReactDom.render(
    <Provider store={store}>
        <Main />
    </Provider>
, document.getElementById('root'));  

store.js

import { createStore } from 'redux';
import todoReducer from './reducers/todoReducer.js';

const store = createStore(todoReducer);

export default store;  

tabReducer.js

    import { ADD_TODO } from '../actions/actionTypes.js';
    
    const initialState = {
          todoList: []
    };
    
    const addTodo = (state, action) => {
    
      return { ...state, todoList: state.todoList.concat(action.obj) }
    }
    
    const todoReducer = (state = initialState, action) => {
      switch(action.type) {
        case ADD_TODO: return addTodo(state, action);
        default: return state;
      }
    };
    export default todoReducer;

Main.jsx

import React from 'react';
import { connect } from 'react-redux';
import { addTodo } from '../actions/todoAction.js';

class Main extends React.Component {

    onClick(){
        let text = this.refs.input;

        this.props.dispatch(addTodo({
            text: text.value
        }))
    }
    render() {
        return (
            <div>
                <input ref="input" type="text"></input>
                <button onClick={()=>this.onClick()}>提交</button>
                <ul>
                {this.props.todoList.map((item, index)=>{
                    return <li key={index}>{item.text}</li>
                })}
                </ul>
            </div>
        );
    }
}

export default connect(
    state => ({
        todoList: state.todoList
    })
)(Main);

todoAction.js

import { ADD_TODO } from './actionTypes.js';

export const addTodo = (obj) => {
  return {
    type: ADD_TODO,
    obj: obj
  };
};

使用webpack-dev-server

webpack-dev-server是一个小型的Node.js Express服务器,它使用webpack-dev-middleware来服务于webpack的包。

1.安装

npm install webpack-dev-server --save

修改在package.json中添加的执行命令:

"scripts": {
  "dev": "./node_modules/.bin/webpack-dev-server --config webpack.config.dev.js",
},

2.配置webpack配置文件:

devServer: {
    "contentBase": devPath,
    "compress": true,
},

contentBase: 表示server文件的根目录
compress: 表示开启gzip

更多的配置文档参考这里

  • webpack-dev-server默认情况下会将output的内容放在内存中,是看不到物理的文件的,如果想要看到物理的dev下面的文件可以安装write-file-webpack-plugin这个插件。

  • webpack-dev-server默认会开启livereload功能

3.devtool功能:

具体来说添加了devtool: 'inline-source-map'之后,利用source-map你在chrome控制台看到的source源码都是真正的源码,未压缩,未编译前的代码,没有添加,你看到的代码是真实的压缩过,编译过的代码,更多devtool的配置可以参考这里

多入口文件配置

在之前的配置中,都是基于单入口页面配置的,entry和output只有一个文件,但是实际项目很多情况下是多页面的,在配置多页面时,有2中方法可以选择:

1.在entry入口配置时,传入对象而不是单独数组,output时利用[name]关键字来区分输出文件例如:

entry: {
    index: [path.resolve(srcRoot,'./page/index/index1.js'),path.resolve(srcRoot,'./page/index/index2.js')],
    detail: path.resolve(srcRoot,'./page/detail/detail.js'),
    home: path.resolve(srcRoot,'./page/home/home.js'),
},
output: {
    path: path.resolve(__dirname, './dev'),

    filename: '[name].min.js'
},

2.通过node动态遍历需要entry point的目录,来动态生成entry:

const pageDir = path.resolve(srcRoot, 'page');
function getEntry() {
    let entryMap = {};

    fs.readdirSync(pageDir).forEach((pathname)=>{
        let fullPathName = path.resolve(pageDir, pathname);
        let stat = fs.statSync(fullPathName);
        let fileName = path.resolve(fullPathName, 'index.js');

        if (stat.isDirectory() && fs.existsSync(fileName)) {
            entryMap[pathname] = fileName;
        }

    });

    return entryMap;
}
{
    ...
    entry: getEntry()
    ...
}

本demo采用的是第二中写法,能够更加灵活。

如何理解entry point(bundle),chunk,module

在webpack中,如何理解entry point(bundle),chunk,module?

根据图上的表述,我这里简单说一下便于理解的结论:

  • 配置中每个文件例如index1.js,index2.js,detail.js,home.js都属于entry point.

  • entry这个配置中,每个key值,index,detail,home都相当于chunk。

  • 我们在代码中的require或者import的都属于module,这点很好理解。

  • chunk的分类比较特别,有entry chunk,initial chunk,normal chunk,参考这个文章

  • 正常情况下,一个chunk对应一个output,在使用了CommonsChunkPlugin或者require.ensure之后,chunk就变成了initial chunk,normal chunk,这时,一个chunk对应多个output。

理解这些概念对于后续使用webpack插件有很大的帮助。

多入口页面html配置

之前我们配置HtmlWebpackPlugin时,同样采用的是但页面的配置,这里我们将进行多页面改造,entryMap是上一步得到的entry:

function htmlAarray(entryMap) {
    let htmlAarray = [];

    Object.keys(entryMap).forEach(function(key){
        let fullPathName = path.resolve(pageDir, key);
        let fileName = path.resolve(fullPathName, key + '.html')
        if (fs.existsSync(fileName)) {
            htmlAarray.push(new HtmlWebpackPlugin({
                chunks: key, // 注意这里的key就是chunk
                filename: key + '.html',
                template: fileName,
                inlineSource:  '.(js|css)'
            }))
        }
    });

    return htmlAarray;

}
修改plugin配置:plugins: [
     ...
].concat(htmlMap)

模块热替换(Hot Module Replacement)

模块热替换 (Hot Module Replacement 或 HMR)是 webpack 提供的最有用的功能之一。它允许在运行时更新各种模块,而无需进行完全刷新,很高大上有木有!

下面说一下配置方法,它需要结合devServer使用:

devServer: {
    hot: true // 开启HMR
},

开启plugin:

const webpack = require('webpack');
plugins: [
    new webpack.NamedModulesPlugin(),
    new webpack.HotModuleReplacementPlugin(),
].concat(htmlMap)

结合React一起使用:

1.安装react-hot-loader

npm install react-hot-loader --save

并新建一个Container.jsx:

import React from 'react';
import Main from './Main.jsx';
import { hot } from 'react-hot-loader'

class Container extends React.Component {

    render() {
        return <Main />
    }
        
}
export default hot(module)(Container);

结合redux:如果项目没有使用redux,可以无需配置后面2步

2.修改store.js新增下面代码,为了让reducer也能实时热替换

if (module.hot) {
    module.hot.accept('./reducers/todoReducer.js', () => {
      const nextRootReducer = require('./reducers/todoReducer.js').default;
      store.replaceReducer(nextRootReducer);
    });
}

3.修改index.js

import ReactDom from 'react-dom';
import React from 'react';
import Container from './Main/Container.jsx';
import store from './store.js';

import { Provider } from 'react-redux';

ReactDom.render(
    <Provider store={store}>
        <Container />
    </Provider>
, document.getElementById('root'));

当控制台看到[WDS] Hot Module Replacement enabled.代表开启成功

使用ESLint

ESLint 是众多 Javascript Linter 中的其中一种,其他比较常见的还有 JSLintJSHint,之所以用 ESLint 是因为他可以自由选择要使用哪些规则,也有很多现成的 plugin 可以使用,另外他对 ES6 还有 JSX 的支持程度跟其他 linter 相比之下也是最高的。

1.安装ESLint

npm install eslint eslint-loader babel-eslint --save

其中eslint-loader是将webpack和eslint结合起来在webpack的配置文件中新增一个eslint-loader种,修改如下:

{ test: /\.(js|jsx)$/, use: [{loader:'babel-loader'},{loader:'eslint-loader'}] ,include: path.resolve(srcRoot)},   

2.新建.eslintrc配置文件,将parser配置成babel-eslint

{
    "extends": ["eslint:recommended"],
    
    "parser": "babel-eslint",

    "globals": {
    },
    "rules": {
    }
} 

3.安装eslint-plugin-react:

npm install eslint-plugin-react --save
  • 说明一下,正常情况下每个eslint规则都是需要在rule下面配置,如果什么都不配置,其实本身eslint是不生效的。

  • eslint本身有很多默认的规则模版,可以通过extends来配置,默认可以使用eslint:recommended。

  • 在使用react开发时可以安装eslint-plugin-react来告知使用react专用的规则来lint。

修改.eslintrc配置文件,增加rules,更多rules配置可以参考这里

{
    "extends": ["eslint:recommended","plugin:react/recommended"],
    
    "parser": "babel-eslint",

    "globals": {
        "window": true,
        "document": true,
        "module": true,
        "require": true
    },
    "rules": {
        "react/prop-types" : "off",
        "no-console" : "off"
    }
}

使用react-router

react-router强大指出在于方便代码管理,结合redux使用更加强大,同时支持web,native更多参考这里

1.安装react-router-dom

npm install react-router-dom --save

2.如果项目中用了redux,可以安装react-router-redux

npm install react-router-redux@next history --save

3.修改代码:

index.js

import ReactDom from 'react-dom';
import React from 'react';
import Container from './Main/Container.jsx';
import { store, history } from './store.js';

import { Provider } from 'react-redux';

import createHistory from 'history/createHashHistory';
import { ConnectedRouter } from 'react-router-redux';

const history = createHistory();

ReactDom.render(
    <Provider store={store}>
        <ConnectedRouter history={history}>
            <Container />
        </ConnectedRouter>
    </Provider>
, document.getElementById('root'));

结合history,react-router一共有3中不同的router:

  • BrowserRouter通过history/createBrowserHistory引入:当切换时,url会动态更新,底层使用的时html5的pushState

  • HashRouter通过history/createHashHistory引入:当切换时,动态修改hash,利用hashchange事件。

  • MemoryRouter 通过history/createMemoryHistory引入:将路径,路由相关数据存入内存中,不涉及url相关更新,兼容性好。

更多配置可以参考这里

4.如果想要在代码逻辑中获取当前的route路径需要引入router-reducer:

新建main.js:

import { combineReducers } from 'redux';
import { routerReducer } from "react-router-redux";
import todoReducer from './todoReducer.js';

const reducers = combineReducers({
  todoReducer,
  router: routerReducer
});
export default reducers;

修改store.js:

import { createStore } from 'redux';
import mainReducer from './reducers/main.js';

const store = createStore(mainReducer);

export default store;

然后就可以在this.props.router里面获取单相关的路径信息

5.如果需要自己通过action来触发router的跳转,需要引入routerMiddleware:

import { createStore,applyMiddleware } from 'redux';
import { routerMiddleware } from "react-router-redux";
const middleware = routerMiddleware(history);
const store = createStore(mainReducer,applyMiddleware(middleware));

6.使用Route和Link和withRouter:

先说说都是干嘛的:

Route:component里面的内容即是tab的主要内容,这个从react-router4开始生效:

<Route exact path="/" component={Div1}></Route>
<Route path="/2" component={Div2}></Route>
  • Link:通常也可以用NavLink,相当于tab按钮,控制router的切换,activeClass表示当前tab处于激活态时应用上的class。

  • withRouter:如果你用了redux,那么你一定要引入它。

export default withRouter(connect(
   state => ({
      todoList: state.todoReducer.todoList
   })
)(Main));

如果你在使用hash时遇到Warning: Hash history cannot PUSH the same path; a new entry will not be added to the history stack错误,可以将push改为replace即:

<NavLink
    replace={true}
    to="/2"
    activeClassName="selected"
    >切换到2号</NavLink>

7.设置初始化路由:

BrowserRouter和HashRouter:

const history = createHistory();
history.push('2');

MemoryRouter:

const history = createMemoryHistory({
    initialEntries: ['/2']
});

使用redux-thunk

redux-thunk 是一个比较流行的 redux 异步 action 中间件,比如 action 中有 setTimeout 或者通过 fetch通用远程 API 这些场景,那么久应该使用 redux-thunk 了。redux-thunk 帮助你统一了异步和同步 action 的调用方式,把异步过程放在 action 级别解决,对 component 没有影响。

1.安装redux-thunk:

npm install redux-thunk --save

2.修改store.js:

import { createStore,applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import mainReducer from './reducers/main';
...
const store = createStore(mainReducer, applyMiddleware(thunk));
...
export default store;

3.在action.js使用redux-thunk:

export const getData = (obj) => (dispatch, getState) => {
  setTimeout(()=>{
    dispatch({
        type: GET_DATA,
        obj: obj
    });
  },1000);
};

使用axios和async/await

axios 是一个基于Promise 用于浏览器和 nodejs 的 HTTP 客户端:

  • 从浏览器中创建 XMLHttpRequest
  • 从 node.js 发出 http 请求
  • 支持 Promise API
  • 自动转换JSON数据

1.安装axios:

npm install axios --save

2.在action中使用axios:

import axios from 'axios';
export const getData = (obj) => (dispatch, getState) => {
    axios.get('/json/comments.json').then((resp)=>{
        dispatch({
            type: GET_DATA,
            obj: resp
        });
    });
};

async/await:

Javascript的回调地狱,相信很多人都知道,尤其是在node端,近些年比较流行的是Promise的解决方案,但是随着 Node 7 的发布,编程终级解决方案的 async/await应声而出。

function resolveAfter2Seconds() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve('resolved');
    }, 2000);
  });
}

async function asyncCall() {
  var result = await resolveAfter2Seconds();
}

asyncCall();

async/await的用途是简化使用 promises 异步调用的操作,并对一组 Promises执行某些操作。await前提是方法返回的是一个Promise对象,正如Promises类似于结构化回调,async/await类似于组合生成器和 promises。

1.async/await需要安装babel-plugin-transform-async-to-generator

npm install babel-plugin-transform-async-to-generator --save

2.在.babelrc中增加配置:

"plugins": [
    "transform-async-to-generator"
]

这样做仅仅是将async转换generator,如果你当前的浏览器不支持generator,你将会收到一个Uncaught ReferenceError: regeneratorRuntime is not defined的错误,你需要:

3.安装babel-plugin-transform-runtime:

npm install babel-plugin-transform-async-to-generator --save

4.修改.babelrc中的配置(可以去掉之前配置的transform-async-to-generator):

"plugins": [
    "transform-runtime"
]

5.如果不想引入所有的polyfill(参考上面对babel的解释),可以增加配置:

"plugins": [
    "transform-runtime",
        {
            "polyfill": false,

            "regenerator": true,
        }
]

6.结合axios使用

import axios from 'axios';
export const getData = (obj) => async (dispatch, getState) => {
    let resp = axios.get('/json/comments.json');
    dispatch({
        type: GET_DATA,
        obj: resp
    });
};

Code Splitting

1.对于webpack1,2之前,你可以使用require.ensure来控制一个组件的懒加载:

require.ensure([], _require => {
    let Component = _require('./Component.jsx');
},'lazyname')

2.在webpack4中,官方已经不再推荐使用require.ensure来使用懒加载功能Dynamic Imports,取而代之的是ES6的import()方法:

import(
  /* webpackChunkName: "my-chunk-name" */
  /* webpackMode: "lazy" */
  'module'
);

不小小看注释里的代码,webpack在打包时会动态识别这里的代码来做相关的配置,例如chunk name等等。

3.Prefetching/Preloading modules:

webpack 4.6.0+支持了Prefetching/Preloading的写法:

import(/* webpackPreload: true */ 'ChartingLibrary');

3.结合React-Router使用:

react-loadable对上述的功能做了封装,丰富了一些功能,结合React-Router起来使用更加方便。

npm install react-loadable --save

在react-router里使用:

function Loading() {
  return <div>Loading...</div>;
}

let Div2 = Loadable({
  loader: () => import('./Div2'), 
  loading: Loading,
});

<Route path="/2" component={Div2}></Route>

使用CommonsChunkPlugin

CommonsChunkPlugin 插件,是一个可选的用于建立一个独立文件(又称作 chunk)的功能,这个文件包括多个入口 chunk 的公共模块。通过将公共模块拆出来,最终合成的文件能够在最开始的时候加载一次,便存起来到缓存中供后续使用。

1.在webpack4之前的用法:

new webpack.optimize.CommonsChunkPlugin({
    name: 'common',
    chunks: ['page1','page2'],
    minChunks: 3
})
  • name: string: 提出出的名称
  • chunks: string[]: webpack会从传入的chunk里面提取公共代码,默认从所有entry里提取
  • minChunks: number|infinity|function(module,count)->boolean: 如果传入数字或infinity(默认值为3),就是告诉webpack,只有当模块重复的次数大于等于该数字时,这个模块才会被提取出来。当传入为函数时,所有符合条件的chunk中的模块都会被传入该函数做计算,返回true的模块会被提取到目标chunk。

更多的参数配置,可以参考这里

2.在webpack4之后的用法:

module.exports = {
  //...
  optimization: {
    splitChunks: {
      chunks: 'async',
      minSize: 30000,
      minChunks: 1,
      maxAsyncRequests: 5,
      maxInitialRequests: 3,
      automaticNameDelimiter: '~',
      name: true,
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true
        }
      }
    }
  }
};

  • splitChunks: 配置一个分离chunk(代替老版本的CommonsChunkPlugin)
  • cacheGroups: 自定义配置主要使用它来决定生成的文件:
  • test: 限制范围
  • name: 生成文件名
  • priority: 优先级
  • minSize: number: 最小尺寸必须大于此值,默认30000B
  • minChunks: 其他entry引用次数大于此值,默认1
  • maxInitialRequests: entry文件请求的chunks不应该超过此值(请求过多,耗时)
  • maxAsyncRequests: 异步请求的chunks不应该超过此值
  • automaticNameDelimiter: 自动命名连接符
  • chunks: 值为”initial”, “async”(默认) 或 “all”:
  • initial: 入口chunk,对于异步导入的文件不处理
  • async: 异步chunk,只对异步导入的文件处理
  • all: 全部chunk

编辑中可能存在的bug没法实时知道,事后为了解决这些bug,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug


交流

干货系列文章汇总如下,觉得不错点个Star,欢迎 加群 互相学习。

https://github.com/qq449245884/xiaozhi

我是小智,公众号「大迁世界」作者,对前端技术保持学习爱好者。我会经常分享自己所学所看的干货,在进阶的路上,共勉!

关注公众号,后台回复福利,即可看到福利,你懂的。

clipboard.png

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant