随着单页应用发展的愈来愈庞大,拆分js就是第一要务,拆分后的js,就能够根据咱们需求来有选择性的加载了。javascript
因此第一个问题,就是js怎么拆?html
来个demo,先看一下未拆分以前是什么样子: a.js:java
import b from './b.js';
console.log("this is a.js")
const btn = document.querySelector("#btn");
btn.onclick = ()=>{
b();
}
复制代码
b.js:node
export default ()=>{
console.log("this is b");
}
复制代码
html:react
<!DOCTYPE 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>
复制代码
webpack.config.jswebpack
module.exports = {
entry:'./a.js',
output:{
filename:'[name].js'
}
}
复制代码
module.exports = {
entry:'./a.js',
output:{
filename:'[name].js',
chunkFilename:'[name].js'// 设置按需加载后的chunk名字
}
}
复制代码
这里就添加了一句,chunkFilename而已,chunkFilename的做用就是用来给拆分后的chunk们起名字的配置项。 ok,执行webpackes6
仍是只打包出了一个main.js,毫无变化... 不用担忧,这是由于还有设置没搞定。web
// import b from './b.js';
console.log("this is a.js")
const btn = document.querySelector("#btn");
btn.onclick = ()=>{
import('./b').then(function(module){
const b = module.default;
b();
})
}
复制代码
输出文件变成了两个,1个main.js、1个1.js 这个1.js很迷...express
查看一下源码,能够看出来,它其实就是咱们的b.jsnpm
总结一下 :
额,成功报错了...脑阔疼 分析报错:
该配置能帮助你为项目中的全部资源指定一个基础路径。它被称为公共路径(publicPath)
。 修改webpack.config.js
module.exports = {
entry:'./a.js',
output:{
filename:'[name].js',
chunkFilename:'[name].js',// 设置按需加载后的chunk名字
publicPath:'dist/' // 设置基础路径
}
}
复制代码
前面1.js这玩意也不可读啊,有问题也很难明确,webpack,提供了定义按需chunkname的方式,修改a.js:
// import b from './b.js';
console.log("this is a.js")
const btn = document.querySelector("#btn");
btn.onclick = ()=>{
import(/* webpackChunkName: "b" */ './b').then(function(module){
const b = module.default;
b();
})
}
复制代码
在动态引入的语法前,添加了注释,注释就是为chunk命明的方式,结果:
输出了b.js,测试回归一次:
先安装webpack-dev-server,配置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"
}
}
复制代码
修改webpack.config.js
var path = require('path');
module.exports = {
entry:'./a.js',
mode:'development',
output:{
filename:'[name].js',
chunkFilename:'[name].js',// 设置按需加载后的chunk名字
publicPath:'dist/'
},
devServer: {
contentBase: './',
compress: true,
port: 9000
}
}
复制代码
修改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',// 设置按需加载后的chunk名字
publicPath:'dist/'
},
devServer: {
contentBase: './',
compress: true,
port: 9000,
hot: true, // 开启热更新
},
plugins: [ // 开始热更新
new webpack.NamedModulesPlugin(),
new webpack.HotModuleReplacementPlugin()
],
}
复制代码
上面一共起做用的就是3句话:
// import b from './b.js';
console.log("this is a.js")
const btn = document.querySelector("#btn");
btn.onclick = ()=>{
import(/* webpackChunkName: "b" */ './b').then(function(module){
const b = module.default;
b();
})
}
if (module.hot) {// 开启热替换
module.hot.accept()
}
复制代码
ok,就这么简单,热更新+按需加载就齐活了。
业务中,除了点击的时候按需加载,还有大部分场景都是在路由切换的时候进行按需加载
修改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',// 设置按需加载后的chunk名字
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()
],
}
复制代码
上面新增的就是添加了一个babel-loader
{
"presets": ["@babel/preset-react","@babel/preset-env"]
}
复制代码
修改a.js
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()
}
复制代码
修改b.js
import React,{Component} from 'react';
export default class B extends Component{
render(){
return <div>this is B</div>
}
}
复制代码
测试一下:
react按需加载进化了好几个方式,目前最新的方式就是使用react-loadable这个组件 官方也推荐使用这个库来实现,目前这个库已经1w+star了
修改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 = () => <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()
}
复制代码
点击跳转toC
能够看到加载了1.js,也就是说异步加载顺利完成 可是如今存在问题:在/C路径下刷新,会出现没法命中路由的状况
var express = require('express')
var app = express()
app.use(express.static('dist'))
app.get('*', function (req, res) {
res.send(`<!DOCTYPE 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="./main.js"></script> </body> </html>`)
})
app.listen(5000);
复制代码
建立一个简单的express应用:
路由一个很常见的功能就是路由嵌套,因此咱们的按需加载必须支持嵌套路由才算合理 修改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: () => 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()
}
复制代码
修改c.js
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: () => 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> } } 复制代码
入口没问题
点击跳转动态加载C没问题
点击跳转D不行了
能够看到动态引入资源./d.js的时候,出现了异常,莫名其妙的添加了路径/C
这里疑惑了好一会,还查了不少内容,最后痛定思痛察觉到应该仍是publicPath设置有问题,从新检查了设置,修改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',// 设置按需加载后的chunk名字
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()
],
}
复制代码
这里惟一的改动,就是publicPath由原来的dist/,变成/dist/,只要把前面的路径补上,就不会去找相对的地址了。
前面看似解决了问题,但在真实场景下,咱们的要求确定会更高! 首先就是要封装一个便捷使用的按需加载组件。
理想很美好,现实很骨干
const LazyLoad = (path)=>{
return Loadable({
loader: () => import(path),
loading: Loading,
})
}
const B = LazyLoad('./b.js')
复制代码
而后就收获了报错
这是由于webpack编译的时候import预发==不支持动态路径==
import不支持动态路径,是由于webpack须要先扫一遍js文件,找出里面按需加载的部分,进行按需打包,但不会关心内部的js执行上下文,也就是说,在webpack扫描的时候,js中的变量并不会计算出结果,因此import不支持动态路径。
既然import不能搞,那只能封装非import的部分了
const LazyLoad = loader => Loadable({
loader,
loading:Loading,
})
复制代码
把loader这部分看成参数分离出去,下面就是具体的使用
const B = LazyLoad(()=>import('./b.js'));
const C = LazyLoad(()=>import('./c.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 LazyLoad = loader => Loadable({
loader,
loading:Loading,
})
const B = LazyLoad(()=>import('./b.js'));
const C = LazyLoad(()=>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()
}
复制代码
上面的封装方式并非十分完美,webpack文档上说支持: ==import(./dynamic/\${path}
)的方式== 只要不全是变量貌似也是支持的,这就要看具体的业务形态了,若是按需的部分都在某个目录下,这种操做或许更温馨一些。
按目前的方式的话,看似比较繁琐,不过能够经过配置webpack的alias别名来进行路径支持。
react router除了组件方式之外,还能够经过config的方式来进行配置,config的方式便于统一维护controller层。
建立LazyLoad.js文件
import React from 'react';
import Loadable from 'react-loadable';
const Loading = (props) => {
return <div>Loading...</div>
};
export default loader => Loadable({
loader,
loading:Loading,
})
复制代码
首先把Lazyload组件单独封装出去
建立routes.js
import LazyLoad from './LazyLoad';
export default [
{
path: "/B",
component: LazyLoad(()=>import('./b.js'))
},
{
path: "/C",
component: LazyLoad(()=>import('./c.js')),
routes: [
{
path: "/C/D",
component: LazyLoad(()=>import('./d.js'))
},
{
path: "/C/E",
component: LazyLoad(()=>import('./e.js'))
}
]
}
];
复制代码
配置routes文件,用来动态引入路由
建立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} />
)}
/>
);
复制代码
==这一步特别重要、特别重要、特别重要==
这个工具方法的做用就是将组件渲染出来
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() } 复制代码
==注意:这里只处理了第一层路由== ==注意:这里只处理了第一层路由== ==注意:这里只处理了第一层路由==
路由配置化以后,嵌套子路由要以函数式来书写
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> ); 复制代码
==注意:config嵌套路由,须要逐层,一层一层的经过RouteWithSubRoutes来渲染。== ==新人很容易忽视这一点!== ==新人很容易忽视这一点!== ==新人很容易忽视这一点!==
前面使用config的方式配置了路由,但其实这里也能够混用,就是config方式+组件的方式混合使用。 修改二级路由入口:
import React from 'react';
import { Link,Route} from 'react-router-dom';
//import {RouteWithSubRoutes} from './utils';
import LazyLoad from './LazyLoad';
const D = LazyLoad(() => import('./d.js'))
const E = LazyLoad(() => 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>
);
复制代码
其实,这里的话,就是随便搞了
路由的话,仍是统一维护为好,固然也能够根据业务来自主选择须要的方式!。
脑阔疼的webpack按需加载告一段落了。