webpack是个打包工具,它默认处理js文件,同时也能借助loaders实现对其余类型文件的处理,同时还能用插件来简化咱们的开发流程。
先要安装一下准备环境,node,由于webpcak是基于node的打包工具
其次要安装webpack和webpack-clicss
npm init -y npm install webpack webpack-cli -D
安装好了之后,咱们能够经过命令行直接来进行打包,能够先新建一个index.js文件,而后在命令行:html
npx webpack index.js
打包完成后会有一个默认的打包文件,咱们要想看看效果能够新建一个index.html来引入默认的打包文件,而后看效果。vue
在此以前,咱们先来修改一下目录,让结构更加清晰,咱们新建一个src目录,将index.js放在里面,而后新建一个dist目录,将index.html放在里面node
接着在根目录下新建一个webpack.config.js的文件,而后在里面配置一些打包的参数,react
const path = require('path'); module.exports = { entry:{ 'main':'./src/index.js' }, output:{ filename:'bundle.js', path:path.resolve(__dirname,'dist') } }
配置好了之后,咱们再来配置一下package.json文件,实现脚本打包的功能,jquery
{ "scripts": { "bundle": "webpack" }, }
此时在命令行中直接使用 npm run bundle,等待打包成功后,手动打开咱们的index.html文件看看效果,至此咱们已经完成了webpack的安装和使用。webpack
前面咱们安装并使用了webpack,咱们处理的内容是js文件,那它如何处理其余类型的文件呢?好比说图片,css,字体等等,这就须要用到loaders,也就是说,webpack能认得js文件,但他不认识其余文件,因此须要一个中间人来告诉他该怎么处理.
首先先来看一下图片文件。首先咱们先来截屏一张图片,而后把它放在src/dog.png,而后要把它挂载在index.html上,在index.js中这样来写;css3
import dog from './dog.png'; var img = new Image(); img.src = dog; var demo = ducument.getElementById('demo'); demo.append(img);
写好之后,咱们直接来打包确定会失败,由于webpack不认识这种文件,因此咱们还须要来写一下配置文件,在webpack.config,js中这样来写;es6
module.exports = { module:{ rules:[ { test:/\.(png|jpg|gif)$/, use:{ loader:'file-loader' } } ] } }
写好了之后咱们先来安装一下file-loader,npm install file-loader -D,而后再来打包,npm run bundle,打包完成之后,再来看看index.html的效果,就会发现图片已经可以加载在页面了,咱们成功的处理了图片这种资源,此时打包出来的图片的名字一堆乱码,太难看,因此咱们能够修改一下web
module:{ rules:[ { test:/\.(jpg|png|gif)$/, use:{ loader:"file-loader", options:{ name:"[name].[ext]" } } } ] }
除了可使用file-loader,咱们还可使用另外一种loader来处理图片,那就是url-loader,咱们来npm install url-loader -D,而后把图片处理规则改为url-loader再来看看效果,先把dist里面的打包文件删除,而后再打包,完成后,会发现并无像file-loader那样把图片单独打包出来,咱们在浏览器看一下它的img的src就会发现它被打包成了base64文件,这是它和file-loader的区别,为何会有file-loader和url-loader这两种处理方式呢?这是由于图片的大小是不肯定的,若是很是大,那就单独处理,这样的话页面的加载就不用等这个图片加完了之后再显示,若是很是小,那就直接包含在打包文件内,这样就会使得减小发送http的请求。
其实url-loader也能够经过配置来实现分类打包,利用limit来设置,若是小于某kb就打包在bundle.js中,不然的话就单独打包在img中而后引用,配置以下:
options:{ name:"[name].[ext]", outputPath:"img/", limit:20400 }
就会发现大于20kb的图片就会直接被分开打包,小于20kb就以base64位的方式打包在bundle.js中。
前面咱们已经能处理js和图片资源了,接下来咱们来处理一下css,scss等文件,
其实思路大概都差很少,都是使用对应的loader来进行处理。
首先咱们先新建style.css和style.scss文件,随便写点样式,
//style.css body{ background:green } //style.scss body{ transform:translate(0,0) }
而后在index.js中引入并使用
import './style.css';
引入好了之后,咱们须要去配置一下loader,在webpack.config.js中
{ test:/\.css$/, use:{ loader:["css-loader","style-loader"] } }
这些lodaer加载时是从后向前加载的,css-loader 是将多个css文件合并成一个,style-loader是将合并完成的css文件挂载在html的head,首先咱们先来安装一下,npm install css-loader style-loader -D,而后npm run bundle进行打包,而后打开index,html就会发现页面已经变成了蓝色,说明css这种类型的文件已经能被webpack识别并打包了,咱们再来试试scss文件,一样在webpack.config.js中进行配置
{ test:/\.scss$/, use:[ 'style-loader', 'css-loader', 'sass-loader' ] }
此时咱们再把刚才写好的style.scss文件在index.js中引用
import './style.scss';
而后再来安装一下npm install sass-loader -D,后npm run bundle,会发现有报错,这是由于咱们缺乏了node—sass这个包,安装一下,npm install node-sass -D,而后继续npm run build打包完成后在html中打开,就会发现页面变成了绿色,说明sass文件也能被webpack进行处理了
前面是最基础的处理css文件的方法,那除了这样来处理文件咱们有时候还须要给不一样的css属性添加厂商前缀,这个咱们经过postcss-loader来实现,首先咱们先来在style.css文件中使用一些css3的属性
body{ transform: translate(0,0); }
此时咱们来打包的时候是不会有厂商前缀的,这就须要咱们来写一些规则,
{ test:/\.css$/, use:['style-loader','css-loader','postcss-loader'] }
配置好了之后,咱们还须要新建一个postcss.config.js来对postcss来进行配置,
//postcss.config.js module.exports = { plugins:[require('autoprefixer')] }
先来安装postcss-loader,npm install postcss-loader autoprefixer -D,而后npm run build此时再来看看,就会发现里面自动加了前缀
至此咱们已经能够处理js,css,图片文件,接下来咱们处理字体文件,咱们日常使用的字体都是默认字体,有时候咱们但愿使用一些特殊的字体,那webpack是不认识这些文件的,咱们就来看看它是如何处理这些文件的,首先咱们先下载一种字体,我已经准备好了,你们下载来用就行了,接着咱们在样式文件中使用一下
//style.css font-face { font-family: 'fontnameRegular'; src: url('fontname.ttf'); } div{ font-family: fontnameRegular }
而后在js中引用一下
import './style.css'; var demo = document.getElementById('demo'); var div = document.createElement('div'); div.innerHTML = '我要自学网'; demo.append(div);
若是此时进行打包,确定会失败,试一下,果真失败了,那怎么处理呢?
配置一下loader
{ test:/\.(ttf)$/, use:{ loader:'file-loader' } }
而后打包试试,npm run bundle,在浏览器查看效果,字体就变化了,这样就成功处理了字体文件
上面咱们说的都是关于处理资源的内容,接下来咱们来看一下咱们是如何经过plugins简化操做流程的,先想一下,咱们到目前为止对于打包编译过程里手工作了什么?
源码-清除-新建-打包-浏览-刷新-报错-定位-转码-切换
这些均可以自动化实现,
咱们首先来处理1和2自动的新建index.html并引入打包资源,这须要借助插件html-webpack-plugin来实现,先来安装一下,
npm install html-webpack-plugin -D
安装好了之后,须要在webpack.config.js里配一下,
const HtmlWebpackPlugin = require('html-webpack-plugin'); plugins:[new HtmlWebpackPlugin({ template:'./src/idex.html' })]
//src/index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>html模板</title> </head> <body> <div id="demo"></div> </body> </html>
它是一个插件,因此写在plugins里面,它的做用是在每次打包文件的时候读取模板文件而后插入到新生成的index.html而后把打包好的资源引入文件中。此时咱们呢npm run bundle发现它果真实现了新建HTML文件并引入打包资源。
接着咱们要解决3.自动清空dist目录,你可能会想说,我之前并无手动的清除过dist目录,在我从新bundle之后仍是直接能用啊,那为何还要来清除呢?咱们假想这样的场景,你在打包的时候,把出口的文件名修改了,在打包的时候会生成不一样的打包文件,多修改几回就会使得dist目录很是的臃肿,因此咱们须要清除。
清除一样是靠插件clean-webpack-plugin一样来安装一下
npm install clean-webpack-plugin -D
而后配一下
plugins:[ new CleanWebpackPlugin({ root:path.resolve(__dirname,'dist') }) ]
这个插件会在打包以前被调用而后去清除dist目录下的内容.实现了咱们的目的。
接下来咱们4.输入一次命令持续监控文件变化,而不是每次修改源码就重复的打包,这个能够借助webpackdevserver来实现,首先先来下载
npm install webpack-dev-server -D
接着来配一下,
devServer:{ contentBase:'./dist', open:true }
修改一下启动脚本,
{ "scripts":{ "dev":"webpack-dev-server" } }
此时再来打包一下,npm run dev,就会发现它打包完成后自动打开了浏览器,而后当咱们源码有修改的时候会自动刷新页面.
为何要用这个服务器呢?使用server它的协议就会变成http,这有助于咱们在开发时避免跨域问题。
那咱们如何来区分这两种环境呢?难道每次切换环境的时候都来修改对应的配置么?那太麻烦了,咱们能够把开发环境和线上环境分别写在一个文件里,而后把共有的逻辑写在另外一个文件,在切换环境的时候直接调用环境配置文件便可,
首先是开发环境
//webpack.dev.js const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const CleanWebpackPlugin = require('clean-webpack-plugin'); const webpack = require('webpack'); module.exports = { mode:"development", devtool:"source-map", entry:{ "main":"./src/index.js" }, devServer:{ contentBase:'./dist', open: true, port:8080, hot:true }, module:{ rules:[ { test:/\.js$/, exclude:"/node_modules/", loader:"babel-loader" }, { test:/\.(jpg|png|gif)$/, use:{ loader:'url-loader', options:{ name:'[name].[ext]', outputPath:'images/', limit:1024 } } }, { test:/\.css$/, use:['style-loader','css-loader'] }, { test:/\.scss$/, use:['style-loader', { loader:'css-loader', options:{ modules:true } }, 'sass-loader'] }, { test:/\.(ttf)$/, use:{ loader:'file-loader' } } ] }, plugins:[ new HtmlWebpackPlugin({ template:'./src/index.html' }), new CleanWebpackPlugin({ root:path.resolve(__dirname,'dist') }), new webpack.HotModuleReplacementPlugin() ], optimization:{ usedExports: true }, output:{ filename:'bundle.js', path:path.resolve(__dirname,'dist') } }
接着是线上环境
//webpack.prod.js const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const CleanWebpackPlugin = require('clean-webpack-plugin'); const webpack = require('webpack'); module.exports = { mode:"production", devtool:"cheap-module-source-map", entry:{ "main":"./src/index.js" }, module:{ rules:[ { test:/\.js$/, exclude:"/node_modules/", loader:"babel-loader" }, { test:/\.(jpg|png|gif)$/, use:{ loader:'url-loader', options:{ name:'[name].[ext]', outputPath:'images/', limit:1024 } } }, { test:/\.css$/, use:['style-loader','css-loader'] }, { test:/\.scss$/, use:['style-loader', { loader:'css-loader', options:{ modules:true } }, 'sass-loader'] }, { test:/\.(ttf)$/, use:{ loader:'file-loader' } } ] }, plugins:[ new HtmlWebpackPlugin({ template:'./src/index.html' }), new CleanWebpackPlugin({ root:path.resolve(__dirname,'dist') }) ], output:{ filename:'bundle.js', path:path.resolve(__dirname,'dist') } }
而后咱们来看,这两个里面有太多的重复代码,咱们能够把他抽离出来放在单独的一个文件中,而后在dev和prod中分别来引用common
//wenpack.common.js const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const CleanWebpackPlugin = require('clean-webpack-plugin'); module.exports = { entry:{ "main":"./src/index.js" }, module:{ rules:[ { test:/\.js$/, exclude:"/node_modules/", loader:"babel-loader" }, { test:/\.(jpg|png|gif)$/, use:{ loader:'url-loader', options:{ name:'[name].[ext]', outputPath:'images/', limit:1024 } } }, { test:/\.css$/, use:['style-loader','css-loader'] }, { test:/\.scss$/, use:['style-loader', { loader:'css-loader', options:{ modules:true } }, 'sass-loader'] }, { test:/\.(ttf)$/, use:{ loader:'file-loader' } } ] }, plugins:[ new HtmlWebpackPlugin({ template:'./src/index.html' }), new CleanWebpackPlugin({ root:path.resolve(__dirname,'dist') }) ], output:{ filename:'bundle.js', path:path.resolve(__dirname,'dist') } }
抽离好了common,接下来就是引用了,这里咱们须要使用一个新的插件来完成合并webpack-merge,先来安装一下,
npm install webpack-merge -D
//dev const webpack = require('webpack'); const merge = require('webpack-merge'); const commonConfig = require('./webpack.common.js'); const devConfig = { mode:"development", devtool:"source-map", devServer:{ contentBase:'./dist', open: true, port:8080, hot:true }, plugins:[ new webpack.HotModuleReplacementPlugin() ], optimization:{ usedExports: true } } module.exports = merge(commonConfig,devConfig)
//prod const merge = require('webpack-merge'); const commonConfig = require('./webpack.common.js') const prodConfig = { mode:"production", devtool:"cheap-module-source-map" } module.exports = merge(commonConfig,prodConfig)
此时根目录下有一堆的配置文件,太杂乱了,因此把他们放在build文件夹下,而后修改脚本路径
"scripts": { "bundle": "webpack", "dev": "webpack-dev-server --config ./build/webpack.dev.js", "build": "webpack --config ./build/webpack.prod.js" },
使用es6代码书写业务,这个须要使用babel来进行转码,咱们来写一下
首先,要使用babel就要先来安装
npm install -D babel-loader @babel/core
而后,配置loader
module.exports = { module:{ rules:[ { test:/\.js$/, exclude:"/node_modules/", loader:"babel-loader", options:{ presets:['@babel/preset-env'] } } ] } }
配置好了loader之后,咱们还须要安装一个模块,由于loader只是把babel和webpack联系在一块儿,具体的转换工做仍是由下面这个/preset-env 模块来作的,转换出来的内容有时候一些低版本的浏览器一样也不支持,就须要对他进行在此的兼容使用/polyfill 这个模块
npm isntall @babel/preset-env -D npm install @babel/polyfill --save
写好了之后,咱们来写点es6的代码来试试
//index.js import '@babel/polyfill' const arr = [ new Promise(() => {}), new Promise(() => {}) ]; arr.map(item => { console.log(item); });
而后就会看到,代码被打包成了能够被低版本浏览器识别的es5代码,可是咱们再来考虑一下,咱们只用到了仅有的几个es6的语法,可是他在识别的时候把全部的特性都转换了,这根本不必,另外,有的浏览器默认就支持es6的代码,不须要进行转换就能用 ,根本不用转换就能用啊,这两个点怎么解决呢?咱们能够给他添加一些配置来实现,
options:{ presets:[['@babel/preset-env',{ targets:{ chrome:"67" }, useBuiltIns:"usage" }]] }
总结一下:1.babel-loader @babel/core:使用babel的必要环境,loader负责将babel和webpack联系起来
2.@babel/preset-env:实际进行代码转换的模块
3.@babel/polyfill:转化出来的代码在一些低版本浏览器一样不能识别,用这个模块来兼容
4.targets:{chrome:"67"} :浏览器支持就不用再转换了
5.useBuiltIns:"usage":只转换使用到的es6代码特性
至此咱们就能够写任意的es6代码了,可是babel自己的配置很是多,若是都写在webpack.config.js中,那会很是的臃肿,因此咱们能够把他单独拿出来写在.babelrc中
//.babelrc { "presets":[ [ "@babel/preset-env", { "targets":{ "chrome":"67" }, "useBuiltIns":"usage" } ] ] }
咱们再来打包试一下,npm run bundle 发现没有问题
最后一个内容就是,如何使用框架来写源码?
首先,咱们先安装框架
npm install react react-dom -D
接着写配置,
//.babelrc { "presets":[ [ "@babel/preset-env", { "targets":{ "chrome":"67" }, "useBuiltIns":"usage" } ], "@babel/preset-react" ] }
写点react代码
import React ,{ Component }from 'react'; import ReactDom from 'react-dom'; class App extends Component{ render() { return <div>hello world</div> } } ReactDom.render(<App />,document.getElementById('demo'));
再来运行npm run dev就能看到react代码了。
一样先来安装框架
npm install vue
接着安装loader
npm install -D vue-loader vue-template-compiler
配制一下loader
module:{ rules:[ { test: /\.vue$/, loader: 'vue-loader' } ] }, plugons:[ new VueLoaderPlugin() ]
写点vue代码
//index.vue <template> <div class="rest">{{info}}</div> </template> <script> module.exports = { data(){ return { info:'hello' } } } </script> <style lang="stylus" scoped> .test color:red </style>
挂载在页面上
//index.js import Vue from 'vue'; import App from './index.vue'; const root = document.getElementById('root'); new Vue({ render:(h) => h(App) }).$mount(root)
在项目中,为了统一编码规范,咱们能够用一些工具来帮咱们约束,eslint是最经常使用的规范工具,咱们来使用一下
npm instll eslint -D npm install babel-eslint -D npm install eslint-loader -D npx eslint --init
如今已经有了一个配置文件.eslint.js,可是对于一些错误无法直接在sublimt中显示,咱们就能够用eslint-loader来进行配置,接着咱们来配置一下
//.eslintrc module.exports = { "parser":"babel-eslint" } rules:{}
//dev devServer:{ overlay:true }
//common module:{ rules:[ { test:/\.js$/, use:["babel-loader","eslint-loader"] } ] }
此时,只要有错误就会在浏览器上进行弹出。
至此咱们看一下,全部手工操做的内容咱们如今都变成了自动操做,大大简化了开发的繁琐,
接下来咱们要考虑的问题就是性能问题,咱们在上面的开发过程当中有哪些性能能够提高呢?
首先是1.局部刷新更替的代码,这个可使用HML来进行实现,它的名字叫热替换,当咱们开启了HML就会只刷新更改的代码,咱们举例来看一下
//index.js import './style.css' var div = document.createElement('div'); div.innerHTML = "我要自学网"; var p = document.createElement('p'); p.innerHTML = "51zxw"; var demo = document.getElementById('demo'); demo.append(div); demo.append(p);
//style.css div{ color:green; } p{ color:red }
此时咱们npm run dev开启服务器,会发现页面上渲染好了内容,当咱们去修改div的颜色的时候,页面会整页刷新,可是实际上p的颜色并无变啊,它也被从新加载了一遍,若是只有这两个样式那还好说,样式一旦增多,这绝对是极大的资源浪费啊,因此,就须要只刷新div的样式而不去刷新总体的样式,咱们须要配一下
const webpack = require('webpack'); devServer:{ contentBase:'./dist', open:true, port:8080, hot:true }, plugins:[ new HtmlWebpackPlugin({ template:'./src/index.html' }), new CleanWebpackPlugin({ root:path.rasolve(__dirnam,'dist') }), new webpack.HotModuleReplacement() ]
此时咱们再来重启服务器,而后在修改div颜色的时候,就会发现它不会再整页刷新而是只替换了修改的代码。
上面是针对css代码的热替换,其实js代码一样也能够,咱们举例说明一下
//counter.js function counter(){ var div = document.createElement('div'); div.setAttribute('id','counter'); div.innerHTML = 1; div.onclick = function(){ div.innerHTML = parseInt(div.innerHTML,10)+1; } document.body.appendChild(div) } export default counter;
//number.js function number(){ var div = document.createElement('div'); div.setAttribute('id','number'); div.innerHTML = 3; document.body.appendChild(div); } export default number;
//index.js import counter from './counter.js'; import number from './number.js'; counter() number()
在未开启HML的时候,页面第一次加载,显示p和div,当修改div里的内容时,p里的内容没有改变,但也被刷新了
在开启HML的时候,咱们能够指定只刷新a的内容
//index.js import createDiv from './a.js'; import createP from './b.js'; createDiv(); createP(); if(module.hot){ module.hot.accept('./a.js',function(){ var div = document.getElementById('d'); document.body.remove(div) createDiv() }) }
至此就实现了js代码的HML热更替
接着咱们须要定位出错代码的位置,这个要用soucemap来进行定位,可是开发环境下它是默认开启的,因此咱们先把他关掉
//webpack.config.js module.exports = { devtool:"none" }
而后启动服务器,这时候咱们故意改错代码
console.lo('51zxw')
这是后当咱们点击错误的代码的连接的时候,咱们直接跳到了打包文件里去了,而不是定位到源码里,因此咱们但愿能定位到源码里,咱们再来修改一下配置
module.exports = { devtool:"source-map" }
而后重启服务器,再次点击的时候就会定位到具体的出错源码,此时咱们会发现,dist目录不见了,实际上是webpack为了加快打包速度,把dist目录直接放在了内存中,咱们从新npm run bundle来学习一下,就会发现此时dist目录又出现了,但也多了一个map文件,这个就是soucre-map的对应文件,他会把打包资源和源码关系醉成对应放在这个文件里。知道了怎么用,咱们接下来讲说devtool有哪些参数,各表明什么意思。inline-source-map:直接把对应文件map放在打包文件里
cheap-source-map:只定位到错误行而不是具体错误字符,同时也不处理引入的第三方包带来的错误
module-source-map:既处理业务代码又处理第三方包的错误
eval-source-map:速度最快但错误信息不全面
在开发环境中通常要使用:devtool:cheap-module-eval-source-map
在生产环境中通常要使用:devtool:cheap-module-source-map
咱们在前面已经学会了打包不一样的资源和自动化开发流程,同时也作了一点性能优化,但还有不少的地方能够优化,好比,咱们在前面配置过"useBuiltIns":"usage",这个是为了使得babel在转码的时候只转码咱们用到的es6特性而不是把全部的es6特性都转码,那在咱们本身写的js代码中,若是咱们引入了一个模块,可是只是用了其中导出的一个内容,那其余的内容也会被打包,那有没有办法使得咱们用哪些东西就打包哪些,不用的就全去除掉呢?这个就是TreeShaking,es模块未引用的导出内容所有不加载咱们来实现一下,
export const add = function(a,b){ console.log(a+b) } export const minus = function(a,b){ console.log(a-b) }
//index.js import { add } from './math.js'; add(1,2)
此时咱们打包的时候,这两个方法都会被打包进bundle.js,咱们来配置一下,
optimization:{ usedExports:true }
{ "sideEffects":false }
在线上环境基本上默认开启treeshaking,开发环境使用optionzation来进行触发。
接着咱们来看一下代码分割,有时候咱们的代码里既包括本身写的业务逻辑,又有引入的第三方包,那在用户加载的时候,若是把两个文件打包在一个文件中,下载的速度会很是的慢,因此就须要把代码分割开来,加载两个较小的文件比加载一个较大的文件速度更快。那如何来实现代码分割呢?
首先咱们先来使用一个外来的库loadsh
npm install loadsh -D
//index.js import _ from 'loadsh'; console.log(_.join(['a','b'],'***'))
而后配置common来实现代码分割
optimization:{ splitChunks:{ chunks:"all" } },
此时,loadsh和业务代码就分开打包了
再来看一下懒加载,经过import异步加载模块,也就是只有须要使用这个模块的时候才来加载,举例说明一下
function getComponent(){ return import('loadsh'); var element = document.createElement('div'); element.innerHTML = _.join(['a','b'],'***'); rerun element; } document.addEventListener('click',()=>{ getComponent().then(element => { document.body.appendChild(element) }) })
再来看一下prefech,异步代码在网络空闲的时候自动下载,举例说明一下
//index.js document.addEventListener('click',()=>{ import(/*webpackPrefetch:true*/'/.click.js').then( ({default:func}) => { func() }) })
//click.js function handleClick(){ const ele = document.createElement('div') ele.innerHTML = "hello"; document.body.append(ele) } export default handleClick;
caching,在进行打包的时候,若是有的包内容没有发生改变,那么先后两次打包的名称就相同,这使得浏览器在请求的时候,若是看到相同的文件就不会去从新发请求,因此能够用contenthash来时的每次修改生成一个新的文件名来避免。
output:{ filename:'[name].[contenthash].js' }
6.shimming:垫片,一种兼容方案,有不少的行为都叫垫片,它主要是弥补一些不足,例如将变量全局化
const webpack = require('webpack'); plugins:[ new webpack.ProvidePlugin({ $:'jquery' }) ]
全局变量
module.exports = (env) => { if(env && env.production){ return merge(commonConfig,prodConfig); }else{ return merge(commonConfig,devConfig); } }
在打包的时候,咱们不免会引入第三方的包,重复的打包时这些没有改变的模块也会从新进行打包,为了加快打包的时间,咱们能够在第一次打包的时候就把它保存下来,在之后打包的时候直接调用就好了,这个要借助Dllplugin来实现。
首先咱们先来引入一些内容来看一下
import React, { Component } from 'react'; import ReactDom from 'react-dom'; class App extends Component{ render() { return <div>hello world</div> } } ReactDom.render(<App />,document.getElementById('demo'));
而后新建一个webpack.dll.js
const path = require('path'); const webpack = require('webpack'); module.exports = { mode:'development', entry:{ vendors:['react','react-dom'] }, output:{ filename:'[name].dll.js', path:path.resolve(__dirname,'../dll'), library:'[name]' }, plugins:[ new webpack.DllPlugin({ name:'[name]', path:path.resolve(__dirname,'../dll/[name].manifest.json'), }) ] }
先来打包一次,生成模块对应的打包文件和json对应文件
{ "scripts":{ "build:dll": "webpack --config ./build/webpack.dll.js" } }
接下来咱们须要在index.html中引入这个文件,
//common.js const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin'); plugins:[ new AddAssetHtmlWebpackPlugin({ filepath:path.resolve(__dirname,'../dll/vendors.dll.js') }) ]
引入成功后,当打包时在commonjs中来检查是否是有vendors.manifest.json中已经打包的内容。若是有就直接在全局变量中使用,没有就去node_modules寻找后再打包,
//common.js plugins:[ new webpack.DllReferencePlugin({ manifest:path.resolve(__dirname,'../dll/vendors.manifest.json') }) ]
就会发现打包的速度确实加快了,若果在大型项目里,咱们须要引入多个模块,须要重复的配太麻烦,因此坐下自动插入
//common const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const CleanWebpackPlugin = require('clean-webpack-plugin'); const webpack = require('webpack'); const devConfig = require('./webpack.dev.js'); const prodConfig = require('./webpack.prod.js'); const merge = require('webpack-merge'); const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin'); const fs = require('fs'); const plugins = [ new HtmlWebpackPlugin({ template:"./src/index.html" }), new CleanWebpackPlugin({ root:path.resolve(__dirname,'../dist') }), new webpack.ProvidePlugin({ $:'jquery' }) ] const files = fs.readdirSync(path.resolve(__dirname,'../dll')); files.forEach(file => { if(/.*\.dll.js/.test(file)){ plugins.push(new AddAssetHtmlWebpackPlugin({ filepath:path.resolve(__dirname,'../dll',file) })) } if(/.*\.manifest.json/.test(file)){ plugins.push(new webpack.DllReferencePlugin({ manifest:path.resolve(__dirname,'../dll',file) })) } }) // module.exports = { const commonConfig = { entry:{ "main":"./src/index.js" }, module:{ rules:[ { test:/\.js$/, exclude: /node_modules/, use:[ "babel-loader", // "eslint-loader", // { // loader:"imports-loader?this=>window" // } ] }, { test:/\.(png|jpg|gif)$/, use:{ loader:"url-loader" } }, { test:/\.(ttf|eot|svg)$/, use:{ loader:"file-loader" } } ] }, plugins, optimization:{ usedExports:true, splitChunks:false }, output:{ path:path.resolve(__dirname,'../dist') } } module.exports = (env) => { if(env && env.production){ return merge(commonConfig,prodConfig); }else{ return merge(commonConfig,devConfig); } }
1.采用最新版本的node
2.loader和plugin做用在有需求的模块上,像第三方模块上尽可能就不检查了
3.resolve
resolve:{ extends:['js','jsx'], alias:{ child:path.resolve(__diename,'/') } }
4.DllPlugins
5.控制包的大小
6.合理使用sourcemap
7.结合stats.json进行分析
8.开发环境内存编译
9.开发环境剔除不用的插件
总结一下提高性能:
1.treeshaking:摇掉引入模块中未使用的内容
//dev optimization:{ usedExports:true }
//package { "sideEffects":false }
2.codesplitting:分块打包代码
optimization:{ splitChunks:{ chunks:"all", minSize:30000, minChunks:1, name:true, cacheGroup:{ vendors:{ vendors:{ test:/[\\/]node_modules[\\/]/, priority:-10, filename:'vendors.js' } }, default:{ priority:-20, reuseExistingChunk:true filename:"common.js" } } } },
npm install mini-css-extract-plugin -D//拆分 npm install optimize-css-assets-webpack-plugin -D//压缩
//prod const MiniCssExtractPlugin = require('mini-css-extract-plugin'); module:{ rules:[ { test:/\.css$/, use:[ MiniCssExtractPlugin.loader, "css-loader", "postcss-loader" ] }, { test:/\.scss$/, use:[ MiniCssExtractPlugin.loader, "css-loader", "sass-loader" ] } ] }, optimization:{ minimizer:[new OptimizeCssAssetsWebpackPlugin({})] }, plugins:[ new MiniCssExtractPlugin({ filename:'[name].css', chunkFilename:'[name].chunk.css' }) ] //注意避免和optimization冲突,而且把common中的css处理摘出来后把prod中也配上
3.lazy-loading:懒加载,只有在调用的时候才去加载模块
function getComponent(){ return import('loadsh'); var element = document.createElement('div'); element.innerHTML = _.join(['a','b'],'***'); rerun element; } document.addEventListener('click',()=>{ getComponent().then(element => { document.body.appendChild(element) }) })
4.preFech:异步代码在网络空闲的时候自动下载
document.addEventListener('click',()=>{ import(/*webpackPrefetch:true*/'/.click.js').then( ({default:func}) => { func() }) })
5.caching:huancun
output:{ filename:'[name].[contenthash].js' }
6.shimming:垫片,一种兼容方案,将变量全局化
const webpack = require('webpack'); plugins:[ new webpack.ProvidePlugin({ $:'jquery' }) ]
全局变量
module.exports = (env) => { if(env && env.production){ return merge(commonConfig,prodConfig); }else{ return merge(commonConfig,devConfig); } }
loader是用来处理各类资源的,它本质上是一个函数,咱们来写一下,
//webpack.config.js const path = require('path'); module.exports = { mode:'development', entry:{ main:"./src/index.js" }, resolveLoader:{ modules:['node_modules','./loaders'] }, module:{ rules:[ { test:/\.js$/, use:{ loader:'my-loader.js', options:{ name:'kk' } } } ] }, output:{ filename:'[name].js', path:path.resolve(__dirname,'dist') } }
//index.js console.log('hello world');
//my-loader.js //npm install loader-utils -D const loaderUtils = require('loader-utils'); module.exports = function(source){ //1.直接处理参数 const options = loaderUtils.getOptions(this); // return source.replace('lee',options.name); //2.callback处理参数 // const result = source.replace('lee',options.name); // this.callback(null,result); //3.写异步代码 const callback = this.async(); setTimeout(()=>{ const result = source.replace('dell',options.name); callback(null,result) },1000) }
plugin是用来帮助咱们简化一些操做流程,它实质上是一个类,咱们来写一下
//webpack.config.js const path = require('path'); const CopyrightWebpackPlugin = require('./plugins/copyright-webpack-plugin.js'); module.exports = { mode:'development', entry:{ main:"./src/index.js" }, resolveLoader:{ modules:['node_modules','./loaders'] }, plugins:[ new CopyrightWebpackPlugin({ name:'dell' }) ], output:{ filename:'[name].js', path:path.resolve(__dirname,'dist') } }
//plugins/copyright-webpack-plugin.js class CopyrightWebpackPlugin{ constructor(options){ cosnole.log(options) } apply(compiler){ compiler.hooks.emit.tapAsync('CopyrightWebpackPlugin',(compiltion,cb)=>{ compiltions.assets['kk.txt'] = { source:function(){ return 'kk' }, size:function(){ return 2; } }; cb(); }) } } module.exports = CopyrightWebpackPlugin;
bundller实质上是要寻找层层引用,而后转化代码使之执行,咱们来写一下
const fs = require('fs'); const path = require('path'); const parser = require('@babel/parser'); const traverse = require('@babel/traverse').default; const babel = require('@babel/core'); //分析一个模块 const moduleAnalyser = (filename) => { //1.读取文件 const content = fs.readFileSync(filename,'utf-8'); //2.得到抽象语法树 const ast = parser.parse(content,{ sourceType:'module' }); //3.找出第一个模块中引入模块的相对/绝对地址,转化后的代码 const dependencies = {}; traverse(ast,{ ImportDeclaration({ node }){ const dirname = path.dirname(filename); const newFile = './' + path.join(dirname,node.source.value); dependencies[node.source.value] = newFile; } }); const { code } = babel.transformFromAst(ast,null,{ presets:['@babel/preset-env'] }); return { filename, dependencies, code } } //4.遍历全部的模块 const makeDenpendencesGraph = (entry) => { const entryModule = moduleAnalyser(entry); const graphArray = [ entryModule ]; for(let i = 0; i<graphArray.length; i++){ const item = graphArray[i]; const { dependencies } = item; if(dependencies){ for(let j in dependencies){ graphArray.push( moduleAnalyser(dependencies[j]) ); } } } const graph = {}; graphArray.forEach(item => { graph[item.filename] = { dependencies:item.dependencies, code:item.code } }); return graph; } //5.生成打包后的代码 const generateCode = (entry) => { const graph = JSON.stringify(makeDenpendencesGraph(entry)) return ` (function(graph){ function require(module){ function localRequire(relativePath){ return require(graph[module].dependencies[relativePath]); } var exports = {}; (function(require,exports,code){ eval(code); })(localRequire,exports,graph[module].code) return exports; }; require('${entry}') })('${graph}') `; } const code = generateCode('./src/index.js'); console.log(code)
终于写完了,参考教程放在下面:
https://coding.imooc.com/clas...