Q1: What is loading on demand?

With the development of single page applications more and more large, split JS is the first priority, split JS, according to our needs to be selectively loaded.

So the first question is, how do I take js apart?

Q2: How to dismantle js?

1. What did it look like before it was split?

Here’s a demo to see what it looks like before splitting: A.js:

import b from './b.js';
console.log("this is a.js")
const btn = document.querySelector("#btn");
btn.onclick = (a)= >{
  b();
}
Copy the code

b.js:

export default() = > {console.log("this is b");
}
Copy the code

html:


      
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="btn">btn</div>
  <script src="./dist/main.js"></script>
</body>
</html>
Copy the code

webpack.config.js

module.exports = {
  entry:'./a.js'.output: {filename:'[name].js'}}Copy the code
  1. A. s reference b.j s
  2. Webpack packs b and A together and outputs a default main.js
  3. The HTML reference to the packaged main.js result is as follows:


2. Let’s do it!

Step1: modify webpack.config.js

module.exports = {
  entry:'./a.js'.output: {filename:'[name].js'.chunkFilename:'[name].js'// Set the name of the chunk loaded on demand}}Copy the code

ChunkFilename is the configuration item used to name the chunks after the split. Ok, execute Webpack

Or is it just a main.js package, nothing changed… Don’t worry, it’s because we still have Settings to work out.


Step2: modify a.js

// import b from './b.js';
console.log("this is a.js")
const btn = document.querySelector("#btn");
btn.onclick = (a)= >{
    import('./b').then(function(module){
      const b = module.default; b(); })}Copy the code
  1. Use es6’s Import on demand syntax
  2. Execute webPack again after the promise and return the result:

The output file becomes two, one main.js and one 1.js.

Looking at the source code, you can see that it is actually our B.JS

To summarize:

  • The output setting in WebPack does not determine whether the code is split
  • The determinant of the split code is the import syntax
  • Webpack only decides to split the code when it detects import syntax in the code

Step3: How to use it?

Well, successful error was reported… Brain pain analysis error:

  • The file for load on demand is /1.js
  • But the result of our package is in the dist directory, which is, of course, impossible to find in the root directory

Step4: set Public Path to the basic Path

This configuration helps you specify a base path for all resources in your project. This is called a publicPath. Modify the webpack. Config. Js

module.exports = {
  entry:'./a.js'.output: {filename:'[name].js'.chunkFilename:'[name].js'.// Set the name of the chunk loaded on demand
    publicPath:'dist/' // Set the base path}}Copy the code

Step5: verify the result


  • Click on the former
  • Only main.js is referenced


  • After clicking on
  • Loaded 1. Js
  • And execute the JS code in 1.js
  • The console outputs this is B.JS
  • Ok, the authentication is successful

Step6: fill in the pit

Webpack provides a way to define chunkname on demand, modifying a.js:

// import b from './b.js';
console.log("this is a.js")
const btn = document.querySelector("#btn");
btn.onclick = (a)= >{
  import(/* webpackChunkName: "b" */ './b').then(function(module){
    const b = module.default; b(); })}Copy the code

Before the syntax is introduced dynamically, a comment is added, which is the way chunk is defined, resulting in:

Output B.js, test regression once:


  • The chunk name has no effect on on-demand loading
  • The name of On-demand Chunk has been changed just for readability

Q3: Can you hot update after loading on demand?

1, run a webpack-dev-server integration first

Install webpack-dev-server and configure NPM scripts

{
  "devDependencies": {
    "webpack-dev-server": "^ 3.1.9." "
  },
  "scripts": {
    "start:dev": "webpack-dev-server"
  },
  "dependencies": {
    "webpack": "^ 4.20.2"."webpack-cli": "^ 3.1.2." "}}Copy the code

Modify the webpack. Config. Js

var path = require('path');
module.exports = {
  entry:'./a.js'.mode:'development'.output: {filename:'[name].js'.chunkFilename:'[name].js'.// Set the name of the chunk loaded on demand
    publicPath:'dist/'
  },
  devServer: {
    contentBase: '/'.compress: true.port: 9000}}Copy the code
  • This time it is not executed through the webpack command
  • Instead, it is executed from the NPM run start:dev command line
  • Webpack-dev-server reads the devServer configuration in webpack.config.js
  • Ok, devServer is integrated

2. Run and see

Modify the webpack. Config. Js

var path = require('path');
var webpack = require('webpack');
module.exports = {
  entry:'./a.js'.mode:'development'.output: {filename:'[name].js'.chunkFilename:'[name].js'.// Set the name of the chunk loaded on demand
    publicPath:'dist/'
  },
  devServer: {
    contentBase: '/'.compress: true.port: 9000.hot: true.// Start hot update
  },
  plugins: [ // Start the hot update
      new webpack.NamedModulesPlugin(),
      new webpack.HotModuleReplacementPlugin()
  ],
}
Copy the code

There are three sentences that work:

  • Hot statement in devServer
  • The two webpack built-in plugins in the plugins have both plugins enabled, but they still don’t work, and you need to modify the entry file
// import b from './b.js';
console.log("this is a.js")
const btn = document.querySelector("#btn");
btn.onclick = (a)= >{
  import(/* webpackChunkName: "b" */ './b').then(function(module){
    const b = module.default; b(); })}if (module.hot) {// Turn on hot replacement
     module.hot.accept()
}
Copy the code

Ok, that’s it, hot update + on-demand loading.


Q4: React-Router integration loads on demand

The react – the router documentation

In business, in addition to on-demand loading when clicking, most scenarios are also loaded on demand when switching routes

Step1: Add babel-Loader

Modify the webpack. Config. Js

var path = require('path');
var webpack = require('webpack');
module.exports = {
  entry:'./a.js'.mode:'development'.output: {filename:'[name].js'.chunkFilename:'[name].js'.// Set the name of the chunk loaded on demand
    publicPath:'dist/'
  },
  module: {
    rules: [{test: /\.js$/.exclude: /(node_modules|bower_components)/.use: {
          loader: 'babel-loader'}}]},devServer: {
    contentBase: '/'.compress: true.port: 9000.hot: true,},plugins: [
      new webpack.NamedModulesPlugin(),
      new webpack.HotModuleReplacementPlugin()
  ],
}
Copy the code

What’s new is the addition of a Babel-Loader


Step2: Add.babelrc

{
  "presets": ["@babel/preset-react"."@babel/preset-env"]}Copy the code

Step3: Write JSX

Modify the a. s.

import React,{Component} from 'react';
import ReactDom from 'react-dom';
import B from './b.js';
export default class A extends Component{
  render(){
    return <div>
      this is A
      <B />
    </div>
  }
}
ReactDom.render(<A/>.document.querySelector("#btn"))
if (module.hot) {
     module.hot.accept()
}
Copy the code

Modify the b.j s

import React,{Component} from 'react';
export default class B extends Component{
  render(){
    return <div>this is B</div>}}Copy the code

Test it out:

  • React is running
  • Hot updates still work

Step4: integrationreact-loadable

React loading on demand has evolved in several ways. The latest way is to use the react-loadable component. It is recommended to use this library, which has been 1W + STAR

Modify the a. s.

import React,{Component} from 'react'; import { BrowserRouter as Router, Route, Switch,Link } from 'react-router-dom'; import ReactDom from 'react-dom'; import Loadable from 'react-loadable'; const Loading = () => <div>Loading... </div>; const B = Loadable({ loader: () => import('./b.js'), loading: Loading, }) const C = Loadable({ loader: () => import('./C.js'), loading: Loading, }) export default class A extends Component{ render(){ return <div> <Router> <div> <Route path="/B" component={B}/> <Route path="/C" component={C}/> <Link to="/B">to B</Link><br/> <Link to="/C">to C</Link> </div> </Router> </div> } } ReactDom.render(<A/>,document.querySelector("#btn")) if (module.hot) { module.hot.accept() }Copy the code
  • The import syntax used in loadable is a dynamic loading feature that ECMA will support in the future
  • Loadable is simple. It simply wraps the components that need to be loaded in the syntax specified by it

Click jump toC

You can see that 1.js is loaded, which means that the asynchronous loading is complete, but now there is a problem: the route cannot be matched when refreshing in /C


Step5: Run an express to verify

var express = require('express')
var app = express()
app.use(express.static('dist'))


app.get(The '*'.function (req, res) {
  res.send(` 
         
      Document
btn
`
) }) app.listen(5000); Copy the code

Create a simple Express application:

  • Verification by
  • Loading on demand is also performed

Step6: the embedded routine shall be loaded on demand

A very common feature of routing is routing nesting, so our load on demand must support nested routing in order to properly modify a.js

import React,{Component} from 'react';
import { BrowserRouter as Router, Route, Switch,Link } from 'react-router-dom';
import ReactDom from 'react-dom';
import Loadable from 'react-loadable';

const Loading = (props) = > {
  return <div>Loading...</div>
};
const B = Loadable({
  loader: (a)= > import('./b.js'),
  loading: Loading,
})
const C = Loadable({
  loader: (a)= > import('./c.js'),
  loading: Loading,
})
export default class A extends Component{
  render(){
    return <div>
      <Router>
        <div>
          <Route path="/B" component={B}/>
          <Route path="/C" component={C}/>
          <Link to="/B">to B</Link><br/>
          <Link to="/C">to C</Link>
        </div>
      </Router>
    </div>
  }
}
ReactDom.render(<A/>,document.querySelector("#btn"))
if (module.hot) {
     module.hot.accept()
}

Copy the code

Modify the SAN Antonio s

import React,{Component} from 'react';
import { Route,Link} from 'react-router-dom';
import Loadable from 'react-loadable';

const Loading = (props) = > {
  return <div>Loadingc...</div>
};

const D = Loadable({
  loader: (a)= > import('./d.js'),
  loading: Loading,
})
export default class C extends Component{
  render(){
    return <div>
      this is C
      <Route path="/C/D" component={D}/>
      <Link to="/C/D">to D</Link>
    </div>}}Copy the code
  • The entry file imports two dynamic routes B and C
  • The route /C/D is nested in the JS
  • On Demand component D is used in routing /C/D

Step7: verify the embedded routine

No problem with entrance


Click jump to load C dynamically


Go to Jump D


You can see that an exception occurred during the dynamic introduction of resources./d.js, and the path /C was inexplicably added


Step8: Damn itpublicPath

After a long time of confusion and a lot of research, I finally realized that there should still be a problem with the publicPath setting. I rechecked the Settings and modified webpack.config.js

var path = require('path');
var webpack = require('webpack');
module.exports = {
  entry:'./a.js'.mode:'development'.output: {path:path.resolve(__dirname, 'dist'),
    filename:'[name].js'.chunkFilename:'[name].js'.// Set the name of the chunk loaded on demand
    publicPath:'/dist/'
  },
  module: {
    rules: [{test: /\.js$/.exclude: /(node_modules|bower_components)/.use: {
          loader: 'babel-loader'}}]},devServer: {
    contentBase: '/'.compress: true.port: 9000.hot: true,},plugins: [
      new webpack.NamedModulesPlugin(),
      new webpack.HotModuleReplacementPlugin()
  ],
}
Copy the code

The only thing that’s changed here is that publicPath is changed from dist/ to /dist/, so you just fill in the previous path, so you don’t have to look for the relative address.


Q5: How to do it in real projects?

The above seems to solve the problem, but in the real world, our requirements will certainly be higher! The first is to encapsulate an on-demand component that is easy to use.

Step1: Encapsulates the LazyLoad component

Ideal is very beautiful, reality is very backbone

const LazyLoad = (path) = >{
  return Loadable({
    loader: (a)= > import(path),
    loading: Loading,
  })
}

const B = LazyLoad('./b.js')
Copy the code

And then you get an error

This is because webPack compilation does not support dynamic path == when import is preissued ==


Step2: The dreaded import, learn about it

Import does not support dynamic paths, because WebPack needs to scan js files first to find out the parts that are loaded on demand, and then package them on demand, but it does not care about the internal JS execution context. That is to say, when WebPack scans, the variables in JS do not calculate the results, so import does not support dynamic paths.


Step3: encapsulate the non-import parts

Since import is not allowed, it can only encapsulate non-import parts

const LazyLoad = loader= > Loadable({
  loader,
  loading:Loading,
})
Copy the code

With the loader part as the parameter, the following is the specific use

const B = LazyLoad((a)= >import('./b.js'));
const C = LazyLoad((a)= >import('./c.js'));
Copy the code

Here is the full code

import React,{Component} from 'react';
import { BrowserRouter as Router, Route, Switch,Link } from 'react-router-dom';
import ReactDom from 'react-dom';
import Loadable from 'react-loadable';

const Loading = (props) = > {
  return <div>Loading...</div>
};

const LazyLoad = loader= > Loadable({
  loader,
  loading:Loading,
})
const B = LazyLoad((a)= >import('./b.js'));
const C = LazyLoad((a)= >import('./c.js'));

export default class A extends Component{
  render(){
    return <div>
      <Router>
        <div>
          <Route path="/B" component={B}/>
          <Route path="/C" component={C}/>
          <Link to="/B">to B</Link><br/>
          <Link to="/C">to C</Link>
        </div>
      </Router>
    </div>
  }
}
ReactDom.render(<A/>,document.querySelector("#btn"))
if (module.hot) {
     module.hot.accept()
}

Copy the code

==import(./dynamic/\${path}) == as long as it is not all variables, it seems to be supported, depending on the type of business, if the required parts are in a directory, this operation may be more comfortable.

This may seem cumbersome in its current form, but you can configure WebPack’s alias for path support.


Q6: Load on demand + Router Config

In addition to component mode, react Router can also be configured through config mode, which facilitates unified maintenance of the controller layer.

Step1: Encapsulate LazyLoad

Create the lazyload.js file

import React from 'react';
import Loadable from 'react-loadable';
const Loading = (props) = > {
  return <div>Loading...</div>
};

export default loader => Loadable({
  loader,
  loading:Loading,
})
Copy the code

First, wrap the Lazyload component separately


Step2: configure routes

Create routes. Js

import LazyLoad from './LazyLoad';
export default[{path: "/B".component: LazyLoad((a)= >import('./b.js'))}, {path: "/C".component: LazyLoad((a)= >import('./c.js')),
    routes: [{path: "/C/D".component: LazyLoad((a)= >import('./d.js'))}, {path: "/C/E".component: LazyLoad((a)= >import('./e.js'))}]}];Copy the code

Configure the routes file to dynamically import routes


Step3: encapsulation tool methodRouteWithSubRoutes

Create utils. Js

import React from 'react';
import {Route} from 'react-router-dom';
export const RouteWithSubRoutes = route= >( <Route path={route.path} render={props => ( // pass the sub-routes down to keep nesting <route.component {... props} routes={route.routes} /> )} /> );Copy the code

== This step is very important, very important, very important ==

The utility method is used to render the component


Step4: modify the layer 1 route entry

import React,{Component} from 'react';
import { BrowserRouter as Router, Route, Switch,Link } from 'react-router-dom';
import ReactDom from 'react-dom';

import {RouteWithSubRoutes} from './utils';
import routes from './routes';

export default class A extends Component{
  render(){
    return <div>
      <Router>
        <div>
          <Link to="/B">to B</Link><br/>
          <Link to="/C">to C</Link>
          {routes.map((route, i) => <RouteWithSubRoutes key={i} {. route} / >)}
        </div>
      </Router>
    </div>
  }
}
ReactDom.render(<A/>,document.querySelector("#btn"))
if (module.hot) {
     module.hot.accept()
}
Copy the code
  1. Introduce the RouteWithSubRoutes tool method
  2. Import routes Route configuration file
  3. Routes traversal render in the package file

== Note: Only layer 1 routes are processed here == == Note: Only layer 1 routes are processed here == == Note: Only Layer 1 routes are processed here ==


Step5: modify the secondary route entry

After routing configuration, nested subroutes are written in a functional form

import React,{Component} from 'react';
import {RouteWithSubRoutes} from './utils';
import { Link} from 'react-router-dom';

export default ({ routes }) => (
  <div>
    this is C
    <Link to="/C/D">to D</Link>
    <Link to="/C/E">to E</Link>
    {routes.map((route, i) => <RouteWithSubRoutes key={i} {. route} / >)}
  </div>
);
Copy the code
  1. Introduce the RouteWithSubRoutes tool method
  2. The exposed function takes one parameter, routes
  3. Routes is the configuration in the inner layer of config, also known as the secondary route configuration
  4. Secondary route configuration continues to render through RouteWithSubRoutes

== Note: Config inserts are rendered layer by layer with RouteWithSubRoutes. == == This is easy for newcomers to overlook! == == This is easy for newcomers to overlook! == == This is easy for newcomers to overlook! = =


Q7: Do you want to use router?

The route is configured using the config method, but it can also be mixed here, that is, the config method + component method is mixed. Modify the secondary route entry:

import React from 'react';
import { Link,Route} from 'react-router-dom';
//import {RouteWithSubRoutes} from './utils';
import LazyLoad from './LazyLoad';

const D = LazyLoad((a)= > import('./d.js'))
const E = LazyLoad((a)= > import('./e.js'))

export default({ routes }) => ( <div> this is C <Route path="/C/D" component={D}/> <Route path="/C/E" component={E}/> <Link to="/C/D">to D</Link> <Link to="/C/E">to E</Link> {/* {routes.map((route, i) => <RouteWithSubRoutes key={i} {... route} />)} */} </div> );Copy the code

Actually, in this case, it’s just casual

Route, or unified maintenance as well, of course, you can also choose the way you need according to the business! .

That’s the end of the mind-numbing webPack load on demand.