再学了前三节以后,其实咱们已经会对文件资源等进行一些打包,可是这些在大型项目中是远远不够的,那么咱们在平时的配置中还会遇到什么难题呢?css
经过本节的学习,你能够学会html
接上一讲,若是咱们在preset-env设置了"useBuiltIns": "usage",那么实际上咱们不去引入babel/polyfill也是能够的。由于咱们在使用useBuiltIns,它会自动帮咱们引入,因此这节咱们直接能够写es6语法。vue
新建一个math.js,而后咱们在 m.js中引入,自行修改打包配置文件,若是你还不会请点击3分钟了解webapcknode
export const add = (a, b) => {
return a + b
}
export const minus = (a, b) => {
return a - b
}
// m.js
import { add } from './math'
console.log(add(1, 3))
复制代码
这个时候咱们虽然实现了效果,可是在打包文件中,我却将个人math文件彻底打包了。这里我却只引入了add方法,因此我是但愿他只打包我引入的文件。因此在package.json中能够作如下配置。jquery
"sideEffects": fasle
复制代码
须要注意的是,这个在线上环境才有用,由于他在开发中会方便咱们去调试。webpack
每次打包(线上,开发)代码以前,咱们都会去不断修改webpack.config.js中的文件,例如modo,插件等之类的,这样的操做是很麻烦的,并且咱们也不可能100%保证咱们就不会改错文件,毕竟改错了文件的影响是很是大的,接下来咱们一块儿看看若是区分线上与开发环境的配置。git
咱们以前有一个webpack.config.js,咱们将其重命名为webpack.dev.js,而后复制一份改名webpack.prod.js。而后根据须要更新以下两个文件。es6
// webpack.dev.js
const path = require('path'); // 从nodejs中引入path变量
const htmlPlugin = require('html-webpack-plugin'); // 引入html打包插件
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const webpack = require('webpack') // 引入webpack插件
module.exports = {
mode: 'development',
devtool: 'cheap-module-eval-source-map',
entry: {
main: './src/m.js',
},
devServer: {
contentBase: './dist', // 借助webpack启动服务器,根目录就是打包以后的dist文件夹
open: true, // 启动npm run start的时候自动打开浏览器
proxy: { // 配置代理
'/api': 'http://localhost:3000'
},
port: 8080, // 配置端口号
hot: true, // 开启热更新
//hotOnly: true // 就算是html文件没生效也不刷新页面
},
module: { // 模块打包配置
// ... dev和prod同样 不写了
},
plugins: [
new htmlPlugin({
template: './index.html'
}),
new CleanWebpackPlugin(),
new webpack.HotModuleReplacementPlugin() // 引入插件
],
output: {
publicPath: '/',
filename: 'dist.js', // 打包后生成的main.js
path: path.resolve(__dirname, 'dist'), // 打包到dist文件夹
}
}
// webpack.prod.js
const path = require('path'); // 从nodejs中引入path变量
const htmlPlugin = require('html-webpack-plugin'); // 引入html打包插件
const cleanPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'production',
devtool: 'cheap-module-source-map',
entry: {
main: './src/m.js',
},
module: { // 模块打包配置
// ...
},
plugins: [
new htmlPlugin({
template: './index.html'
}),
new cleanPlugin(['dist']),
],
output: {
publicPath: '/',
filename: 'dist.js', // 打包后生成的main.js
path: path.resolve(__dirname, 'dist'), // 打包到dist文件夹
}
}
复制代码
ok,文件以及拆分了,这个时候咱们会修改package.json里面的scriptsgithub
"scripts": {
"dev": "webpack-dev-server --config webpack.dev.js",
"build": "webpack --config webpack.prod.js"
},
复制代码
重启服务,打包文件正常运行。这样咱们就区分开了,可是有一个很明显的问题就是,这两个文件,重复的地方太多了,若是我之后新增了一个公共的代码,两个文件都要加,删除也是两个文件都要作。这样就会让个人维护成本变高,并且仍是会增长错误的概率,因此咱们有必要对配置文件进行合并。web
先下载webapck-merge,他能够帮助咱们合并webpack的配置
npm install webpack-merge -D
复制代码
新建webpack.common.js进行代码合并
// webpack.common.js
const path = require('path'); // 从nodejs中引入path变量
const htmlPlugin = require('html-webpack-plugin'); // 引入html打包插件
const cleanPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
main: './src/m.js',
},
module: { // 模块打包配置
// ... 省略
},
plugins: [
new htmlPlugin({
template: './index.html'
}),
new cleanPlugin(['dist']),
],
output: {
publicPath: '/',
filename: 'dist.js', // 打包后生成的main.js
path: path.resolve(__dirname, 'dist'), // 打包到dist文件夹
}
}
// webpck.dev.js
const webpack = require('webpack') // 引入webpack插件
const webpackMerge = require('webpack-merge')
const commonConfig = require('./webpack.common')
const devConfig = {
mode: 'development',
devtool: 'cheap-module-eval-source-map',
devServer: {
contentBase: './dist', // 借助webpack启动服务器,根目录就是打包以后的dist文件夹
open: true, // 启动npm run start的时候自动打开浏览器
proxy: { // 配置代理
'/api': 'http://localhost:3000'
},
port: 8080, // 配置端口号
hot: true, // 开启热更新
//hotOnly: true // 就算是html文件没生效也不刷新页面
},
plugins: [
new webpack.HotModuleReplacementPlugin() // 引入插件
]
}
module.exports = webpackMerge(commonConfig, devConfig)
// webpack.prod.js
const webapckMerge = require('webpack-merge')
const commonConfig = require('./webpack.common')
const prodConfig = {
mode: 'production',
devtool: 'cheap-module-source-map',
}
module.exports = webapckMerge(commonConfig, prodConfig)
复制代码
npm run dev,ok nice.
代码分割,这个我就举例说明一下就ok。
在咱们平时使用vue等大框架的时候,常常会用到一个lodash.js,假设咱们正常的下载并使用改代码。
文件 | 大小 |
---|---|
lodash.js | 1MB |
axin.js | 1MB |
// axin.js
import _ form 'lodash'
// 使用lodash
复制代码
这样的话,假设咱们的代码不作压缩,咱们的代码就会达到2MB大小,若是用户打开咱们的网页,这个时候咱们就会先去加载这个2mb的文件,这样的话,对用户体验很很差。那若是咱们可以达到以下效果,就好多了 。
// lo.js
import _ form 'lodash'
window._ = _
// axin.js
// 使用lodash.js
复制代码
js是支持并行加载的,不能说必定比2m的快,可是至少能优化很多,最大的好处是什么?是咱们若是只是修改了axin.js的内容,那咱们的lo.js是不须要改变的,浏览器中会有缓存,这个时候想要的效果就会明显提高。
那么咱们在webpack中应该如何配置呢?找到webpack.common.js
optimization: {
splitChunks: {
chunks: 'all'
}
},
复制代码
这个时候就会帮咱们去拆分代码,须要特别说明的是,webpack和Code Splitting是没有关系的,默认的会帮咱们下载一个功能,咱们只须要配置便可。
这个是同步加载的方式,有时候咱们的文件是异步回来的,其实也是这么一回事。我就很少作演示。 你们有兴趣的能够本身下来试试。
为了搞清楚,这个插件,仍是没能逃避写一个异步加载的方法来使用组件。
function getComponent(){
return import('lodash').then(({ default: _}) => {
var element = document.createElement('div')
element.innerHTML = _.join(['jsxin', 'hello'], '-')
return element
})
}
getComponent().then(element => {
document.body.appendChild(element)
})
复制代码
// {default: } 加载回来的赋值给_
不管是同步加载或者异步,咱们都会进行代码分割。咱们先来下载一个官方提供的动态引入的插件。
平常直通车:babeljs.io/docs/en/nex…
npm install --save-dev @babel/plugin-syntax-dynamic-import
// .babelrc
{
"plugins": ["@babel/plugin-syntax-dynamic-import"]
}
//package.json
"dev-build": "webpack --config webpack.dev.js",
复制代码
webpack-dev-server会把文件写到内存咱们是观察不到的,因此新增一个命令npm run dev-build,让其打包代码。
这时候给我生成了一个0.dist.js
咱们能够在引入以前使用注释符为其设置名字
function getComponent(){
return import(/* webpackChunkName: "loadash" */'lodash').then(({ default: _}) => {
var element = document.createElement('div')
element.innerHTML = _.join(['jsxin', 'hello'], '-')
return element
})
}
复制代码
就会生成一个vendors~lodash.dist.js
由于这里设置的比较多,咱们简单的把配置项讲解一下,如下为配置项,若是你的splitChunks没有配置任何内容,就会使用如下的内容做为配置项。
optimization: {
splitChunks: {
chunks: 'async', // all 不区分 async 只对异步代码生效
minSize: 30000, // 打包最小30000字节我才去分割
minRemainingSize: 0,
maxSize: 0, // 通常配置 50000 就至关于能拆分红几个50kb左右的
minChunks: 1, // 最少使用一次
maxAsyncRequests: 6, // 同时加载的模块数最多6个
maxInitialRequests: 4, // 入口文件也会拆分 可是最多4个 超过了就不分分割了
automaticNameDelimiter: '~', // 名字和组的拼接符 vendors~lodash
cacheGroups: { // 拆分分组
defaultVendors: { // 默认分组
test: /[\\/]node_modules[\\/]/, // 若是是node_modules中的咱们就到defaultVendors这个组
priority: -10, // 优先级, 和下面default 同时知足条件 打包到优先级高的里面
// filename: 'vendor.js' 能够本身取名字
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true // 好比以前引用了a代码,就不会打包a到common.js,会复用
// filename: 'common.js' 能够本身取名字
}
}
}
},
复制代码
咱们对刚才的异步代码作一点改进
function getComponent(){
return import(/* webpackChunkName: "loadsh" */'lodash').then(({ default: _}) => {
var element = document.createElement('div')
element.innerHTML = _.join(['jsxin', 'hello'], '-')
return element
})
}
document.addEventListener('click', () => {
getComponent().then(element => {
document.body.appendChild(element)
})
})
复制代码
咱们仍是异步加载一个loadsh函数,而后在页面中绑定了一个点击事件,只有咱们监听到点击事件的时候,咱们才回去调用getComponent方法,而后经过getComponent方法去引入loadsh函数。
效果就是咱们在页面中,开始只会加载一个main.js,而后点击一下页面会在加载一个loadsh函数,调用这个函数的某些方法咱们实现了一个字符串的拼接过程,最终呈如今了页面上。
经过import方法,咱们只有访问了在某些文件的时候,他才会异步加载,而后执行。这样咱们加载速度也会更快。
固然后也可使用es7中比较流行的async来处理这个时间,让大家的代码更加直爽。
async function getComponent() {
const { default: _ } = await import(/* webpackChunkName: "loadsh" */'lodash')
const element = document.createElement('div')
element.innerHTML = _.join(['jsxin', 'hello'], '-')
return element
}
复制代码
咱们在以前已经使用了不少次chunk了,那么咱们这个chunk究竟是什么?
在js代码打包中,咱们会拆分红多个js文件,那么每个js文件,咱们都称它为一个chunk。
先来看看官方的webpack分析工具
若是你相对咱们打包以后的代码进行分析,首先你须要将--profile --json > stats.json 放到你打包的命令中
"dev-build": "webpack --profile --json > stats.json --config webpack.dev.js",
复制代码
他的意思就是将个人打包过程放到stats.json这个文件中。
他会将咱们整个打包的流程都写进入,比较耗时,打包了什么资源,有几个模块,几个chunk等,你能够能够借助官方工具帮你翻译一下。这里你们能够了解一下,我就很少作介绍,能够自行打包尝试。
在这个知识点以前咱们先来看看咱们最原始的代码写法。
document.addEventListener('click', () => {
const element = document.createElement('div')
element.innerHTML = 'jsxin'
document.body.appendChild(element)
})
复制代码
这个是咱们经常使用的标准写法,难道这个写法就没有优化空间了吗?
编译成功以后咱们打开f12, 而后按住command+shift+p,输入coverage这个关键词
咱们点击一下show Coverage,而后左侧会出现一个录制按钮,咱们会发现咱们的main.js只有75%的代码使用率。
为何呢?由于咱们在页面加载的使用,咱们并不会使用
const element = document.createElement('div')
element.innerHTML = 'jsxin'
document.body.appendChild(element)
复制代码
这些代码是在被点击的时候才会用到,因此这不是webpack推荐的一种书写代码的方式。
咱们能够将这部分代码这么写,新建click.js
// click.js
export default function addComponent () {
const element = document.createElement('div')
element.innerHTML = 'jsxin'
document.body.appendChild(element)
}
// com.js
document.addEventListener('click', () => {
import('./click.js').then(({ default: _ }) => {
_()
})
})
复制代码
这样的话,他的使用了就达到了79%,也会节约咱们的首屏加载时间。
咱们一个网页,刚开始初始化首页的时候,咱们不加载登陆模态框,先加载首页的其余逻辑,等加载完成以后,带宽被释放出来了,咱们偷偷的加载登陆模态框,这样的话,既知足了我首页加载快的需求,又知足了登陆加载快的需求。
而这个方案就是咱们结合prefetching和preloading的一个比较实用的例子。
能够在import以前声明prefetching配置
import(/* webpackPrefetch: true */'./click.js')
复制代码
这个时候,等他将咱们的核心代码加载完成以后,就会偷偷的加载click.js
咱们生成以下代码
import '../statics/style/index.css'
console.log(123)
复制代码
我如今但愿个人index.css不直接生成css代码到个人页面上,而是但愿他在dist下面新建一个文件夹,而后把css放进去引入,那么这么时候咱们应该怎么处理这种操做呢?
官方插件:webpack.js.org/plugins/min…
特别说明:适合线上环境中使用,由于更新以后不会自动刷新
先来安装一下插件
npm install --save-dev mini-css-extract-plugin
复制代码
而后在线上环境中使用。
// webpack.prod.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
plugins: [new MiniCssExtractPlugin()],
复制代码
而后咱们以前使用的style-loader就不能用了,他给咱们提供了一个loader,咱们将style-loader替换成他的loader,而后还要将css区分开线上与开发环境。
// webpack.prod.js
module: {
rules: [{
test: /\.css$/, // 检测文件是css结尾的
use: [MiniCssExtractPlugin.loader, 'css-loader']
},
{
test: /\.scss$/, // 检测文件是scss结尾的
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
importLoaders: 2, // 经过import引入的scss文件,也要走下面两个loader
// modules: true
}
},
'sass-loader',
'postcss-loader'
]
}]
}
复制代码
而后咱们打包一个线上的代码试试。就在咱们的代码中生成了main.css文件。
咱们简单的看看他的配置项
plugins: [new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[name].chunk.css'
})],
复制代码
若是样式是直接被引用,他就会走filename,间接就是chunkfilename
咱们不妨再来作一些尝试。
// index.css
.avatar{
width: 100px;
height: 100px;
}
// index1.css
.avatar{
display: block;
}
// style.css
import '../statics/style/index.css'
import '../statics/style/index1.css'
console.log(123)
复制代码
咱们再次打包,你会发现,他自动将两个样式文件给我合并到main.css中了。
// 打包后
.avatar{
width: 100px;
height: 100px;
}
.avatar{
display: block;
}
/*# sourceMappingURL=main.css.map*/
复制代码
ok,那么咱们若是还想对这个css进行一些压缩怎么办呢?
// install
npm install optimize-css-assets-webpack-plugin -D
// prod
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
optimization: {
minimizer: [new OptimizeCSSAssetsPlugin({})]
}
复制代码
而后在打包,他不只将咱们的代码进行了压缩,还将咱们的代码合并到了一块儿。
// 打包后
.avatar{width:100px;height:100px;display:block}
复制代码
ok,其余更高级的用法,请参考官方文档,这里一方面是和你们一块儿体验,另外一方面是推荐经常使用的插件。
咱们在加载一个网页的时候,咱们可能首先会加载一个index.html,和两个js文件,当你下次访问的时候,其实浏览器已经对你两个js有缓存了,这个时候会优先读取缓存中的文件。
这个时候要么你更改一下文件的名字,要么就强制刷新,可是你确定不能让用户强制刷新页面。因此咱们在调试过程当中能够无论,从新配置一下output
// cache.sj
import _ from 'lodash'
let str = _.join(['j', 's', 'x', 'i', 'n'], '-')
console.log(str)
output: {
filename: '[name].[contenthash].js',
}
复制代码
contenthash就是咱们根据内容生成的一个hash值,只要你的内容没有改变,那么咱们就不用从新去加载这些js。
咱们改变的是什么?咱们会修改本身的逻辑源代码,可是你并不会去改变node_modules的第三方代码,因此这些东西确定仍是可让浏览器读缓存,提升网站加载效率。
webpack是基于模块打包的,也就是说咱们在一个模块里面的代码,到另一个模块就找不到了。
// jq.js
import $ from 'jquery'
import { jqui } from './jq.ui'
jqui()
$('body').append('<div>axin</div>')
// jq.ui.js
export function jqui(){
$('body').css('background','red')
}
// $ is not defined
复制代码
webpack是提供了一下插件的,咱们来看看他是干吗的。
new webpack.ProvidePlugin({
$: 'jquery'
})
复制代码
他会去检测你哪一个文件是使用了$符,若是使用了,那么你是否在上面引入了jquery,若是没有的话,他就会自动帮你在上面引入,很是nice。
还记得咱们使用了一个babel/polyfill吗?若是你没有promise等,他会帮你完成promise的实现。
咱们以前是在dev/prod环境中合并的common,那么这里咱们来使用一个环境变量来从新合并咱们的打包配置文件,先上代码。
// dev/prod 注释如下代码 而后分包导出
const webapckMerge = require('webpack-merge')
const commonConfig = require('./webpack.common')
module.exports = prodConfig
module.exports = devConfig
// webpack.common.js
const webapckMerge = require('webpack-merge')
const prodConfig = require('./webpack.prod')
const devConfig = require('./webpack.dev')
const commonConfig = ...(配置)
module.exports = (env) => {
if(env && env.production){
return webapckMerge(prodConfig, commonConfig)
}
return webapckMerge(devConfig, commonConfig)
}
复制代码
这里咱们直接导出了一个函数,接收了一个env,因此咱们在打包脚本中这么这么写
"scripts": {
"dev-build": "webpack --profile --json > stats.json --config webpack.common.js",
"dev": "webpack-dev-server --config webpack.common.js",
"build": "webpack --env.production --config webpack.common.js"
},
复制代码
配置了env变量,咱们在打包的时候都是webpack.common.js,而后判断就能够不一样的配置进行打包了。
ok,就到这里吧,知识点即总结,若是你也想和我一块儿学习webpack,我们第五节见。