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
- A. s reference b.j s
- Webpack packs b and A together and outputs a default main.js
- 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
- Use es6’s Import on demand syntax
- 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
- Introduce the RouteWithSubRoutes tool method
- Import routes Route configuration file
- 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
- Introduce the RouteWithSubRoutes tool method
- The exposed function takes one parameter, routes
- Routes is the configuration in the inner layer of config, also known as the secondary route configuration
- 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.