📔 简单了解webpack
什么是webpack,这里博主博客有一篇文章已经很系统的说明了,这里再简单的简述一下,急速我们前端web开发的话,为了确保代码可以进行维护,我们把代码拆分成了很多的模块,而这些管理模块的就是我们的webpack。
当然,webpack还提供了摇树、懒加载、代码分割的额外的功能,所以一句话总结,想要更深的区了解前端工程化,代码的背后是别有通洞天的。
一、📃基本配置
1.1✒️学会拆分配置和merge
webpack所谓的拆分的配置就是将webpack分为common(公共配置)、dev(开发环境下的配置)、prod(生产环境下);然后还需要的是在dev当中将它引入我们的common,再在prod当中引入common,这个时候我们要做的就是安装webpack-merge,比如通过这个方法module.export = smart将其导入common的时候对应的配置的命名。
1.2 ✒️学会启动本地服务
学会启动本地服务的步骤,首先启动是依赖的是webpack-dev-serve,不过,当然了,我们也可以进行定义dev,使用我们的webpack-dev-serve或者定义build使用我们的webpack就不一样的,所以启动的时候要注意区分。
1.3 ✒️学会处理ES6
学会处理ES6的方法是,首先知道ES6的配置是在common当中的,因为dev和prd都是要用到的,test的作用是检测所有的js文件。我们的loader文件是引入了一个babel=loader,include是需要检测的文件所在的目录。但是我们的exclude是不需要检测的文件的。
当然了,我们使用babel即使要去配置.babelrc,一般情况下,根据一下的放下配置就好,plugins是配置不在这里面的但是需要使用的,一般还是不需要用的。
1.4 ✒️学会处理样式
我们处理样式当然也是在common当中,因为开发以及线上都是需要的,第一步是需要我们css-loader对css进行解析,将其解析成我们的css,,因为引入是按js那种的引入的方式才能被webpack认为是js文件,然后再通过style-loader将它插入到页面当中,加上我们的postcss的作用是用来兼容浏览器的。
当然如使用得postcss配置的话也是需要postcss.config.js,我们在这里引入需要的细分的模块,当然这一些模块也是需要安装的。module.export = {plugins:[require('autoprefixer')}
1.5 ✒️学会处理图片
学会处理图片的操作是需要分开来写,我们在开发的工程中一般都直接引入图片的地址的,但是这样的方法是对于线上的操作不是很友好的,因为对于那一些小于5KB的图片我们是用base64然后将它打包到img的目录下,因为这样子可以以html标签的方式引入图片是不需要再去请求的,加上图片本身内存不大,请求没有意义。
像一些开发模式都是直接丢在根目录下面的,这样的操作是不好的,因为一般放html。其实img目录下面的只有比较大的图片,小的图片都以base64写到代码当中。
{test:(.png|.jpg|gif)$/,use:{loader:'url-loader',option:{limit:5*1024,outputPath:'/img1/'}
然后,模块化的话,就是webpack本身就是支持模块化的,只不过还需要的是引入webpack,不然的话有一些模块就是识别不了。
二、📃高级配置
2.1 ✒️学会配置多个入口
我们的js文件是在common里面要写入多个entry,因为是要多个页面嘛,然后就是修稿生产环境下的output,那就不要就是直接写死名字,而是应该使用变量,这个可以根据entry里面的命名自动配置的。
entry:{index:path.join(srcPath,'index.js'). other:path.join(srcPath,'other/js')}
html的话需要在common当中进行配置,针对每一个入口都要在new一个新的webpack的实例,分别对应的是html文件,并且chunks标注需要引入的js文件就好了,不写的话,就直接全部引入。
newHtmlWebpackPlugin({template:path.join(srcPath,'index.html'],filename:'index',chunks:['index']}
2.2 ✒️学会抽离压缩CSS
存在问题:CSS一般来说,应该是作为外部链接引入的,而不是直接在style直接出现的。目前的实现是是在打包中将一些CSS代码经过js塞入html,效率比较低,所以应该抽离。
解决方法:安装引入mini-css-extract-plugin、terser-webpack-plugin、optimize-css-aesets-webpack插件,然后进行配置,需要注意的是抽离less,less最后变成css,关键在与使用插件loader而不再是style-loader。
2.3 ✒️学会抽离公共代码和第三方代码
存在问题:我们在之前多个文件都是要引入sum,如此打包的话我们的各个文件都会把sum给打包进来,应该的是单独的抽离出来然后项目引用比较好。另一情况就是我们引入第三方模块的话,而且每次修改的话,只要稍微改一改一点点的业务代码就可以了。但是第三方模块里面,会导致第三方模块也会跟着重新加载,如果可以吧第三方模块拆出来就会命中缓存加载快一些。
解决方法:改动的部分是在生产环境,chunk是一段代码,一般写all就可以了,既然是拆分就需要起名字,所以就要给不同的name,priority代表的是权重,考虑如果是模块之间有相互作用的情况是参考哪一个模块的规则test是路径,第三方模块是属于在这个路径下面的命中了被划分到vendor模块当中,当然如果是包比较小的是我们就直接复制到每一个文件夹,不去拆分;minChunks是引用了多少次就要开始划分模块。
splitChunks:{chunks:all,cacheGroup:{name:'vendor',priority:1,test:/node_modules/,minsize:0,minChunks:1}, common:{{同样配置不同参数}}
2.4 ✒️学会异步加载JS
我们文件较大的时候,需要异步加载的话就不能直接import,而是import路径,通过我们的then去拿到资源res,这个是webpack默认支持的语法,不需要我们做什么配置。
import{ './index.js'} .then((res)=>{具体操作})
注意的是,异步加载的话也会生成一个chunk,与其他的chunk在同一个目录下面。
2.5 ✒️学会处理Vue和jsx
如果是处理jsx可以安装@babel、preset-react,然后再.baberlrc给安排上就可以了。至于vue就直接安装vue-loader然后配置rules就可以了。loader:['vue-loader'],至于是vue的话,那我们就直接安装vue-loader然后再进行配置rules就好了.
2.6 ✒️学会区分module chunk bundle三个区别
- module是各个源码文件,webpack一切都是模块;
- chunk多个模块合成,比如entry、import、splitChunk;
- bundle是最终输出文件的意思;
- 注意:html不是模块,是输出的东西,而一切的都是可以应用的都是模块。最后输出的文件可以不只有一个。
三、📃webpack优化打包构建速度-开发体验和效率
3.1 ✒️学会处理多进程--happypack
为什么要学会这个呢?因为我们的js是单线程的,开启多进程的话打包为了提高构建的速度。我们具体实现的方法是:安装happypack然后早prod生产环境下使用,需要重新配置rules中的babel。
use:['happypack/loader?id=babel'] new HappyPack ({id:'babel',loaders:['babel-loader?cacheDirectory']}) //这里的ID是对应我们接下来在plugin中实例化的happypack实例
3.2 ✒️学会处理多进程--ParelleUglifyPlugin
学习的原因是webpack内置的工具Uglify压缩我们的js、但是js是单线程的,开启多进行压缩就会更加得快。
我们具体的实现的方法是:依然是安装webpack-paraller-uglify-plugin插件,直接在plugin当中去new一下我们的实例就可以了。项目大的时候,打包起来会比较慢,可以开启多进程提高速度,项目小的时候打包较快,开启多进程的话会降低速度,所以根据自己的需求进行选择。
//使用我们的ParallelUlifyPlugin 并行压缩输出我们的js代码 new ParallelUlifyPlugin({ //传递给Ulify参数 UlifyJS:{ output:{ beautify:false,//最紧凑的输出 comments:false,//删除所有的注释 }, compress:{ //删除所有的console语句,可以兼用IE浏览器 dorp_console:true, //内嵌定义但是只用到一次这个变量 collapse_vars:true, //提取出来的出现多次但是没有定义成变量去应用的静态值 reduce_vars:true, } } })
3.3 ✒️了解什么是热更新
定义是:新代码生效,网页不刷新,状态不丢失。
我们实现的方法是:热更新是在我们的dev环境下进行的,直接插入插件webpack/lib/HotModulePeplacementPlugin,我们的入口文件的话就不能再直接写文件的路径,网站需要对应自己的网站地址。插件的使用也就是直接在plugin中直接new一下就还了1,然后再devserve中写上hot属性为TRUE就好了。
//第一点:插入插件 index:[ 'webpack-dev-server/client?http://localhost:8080/', 'webpack/hot/dev-serveer', path.join(srcPath,'index.js') ], //第二点:devServer devServer:{ prot:87080, // 显示打包的进度条 progress: distPath, //目录 open: true,//自动打开浏览器 compress:true,//启动压缩 hot:true, } //第三点:增加,开启热更新之后的代码逻辑 if (module.hot) { module.hot.accept(['.math'],()=>){ const sumRes = sum(10,30) console.log('sumRes in hot',sumRes) } }
3.4 ✒️了解什么是自动更新
定义:在开发环境下我们其实只需要配置devserver就会开启自动更新,线上没有自动刷新的说法。自动刷新的意思是保存代码就会页面刷新。问题是会导致状态的丢失和路由的丢失而由于我们整个页面的刷新速度比较慢,状态的丢失就是比如填好的一些数据没有了,网页已经点了比较深的地方会退回到首页。
module.export = { watch: true,//开启监听,默认为FALSE //注意的是,开启监听后,会自动刷新浏览器 //监听配置 watchOptions: { ignored: /node_modules/, //监听到变化发生后会等到300ms再去执行动作,房子文件更新太大导致重新编译评率大 aggregateTimeout:300,//默认速度 poll:1000 //默认meige11000毫秒询问一次 } }
3.5 ✒️了解怎么优化babel-loader
我们在引入loader的时候加上cacheDirectory,可以让没有修改的部分不在重新编译,也就是缓存了。use:['babel-loader?cacheDirectory'],我们明确的范围是,exclude和include选择一个就好了,include:path.resolve(_dirname,src)
3.6 ✒️了解什么是lgnorePlugin和noPase
这个部分只需要了解就好了,gnorePlugin主要是为了避免一些模块的引入,而noPase避免的是引入的时候打包一些东西。
注意:以上出来自动更新、热更新是考虑开发更加流畅不能用于生产环境,其他都可以用!
四、📃webpack优化产出代码--产品性能
4.1 ✒️学会处理bundle+hash
bundle+hash,内容改变了hash也会跟着改变,缓存就会失效,这个时候再去重新请求,不然就会命中缓存,加载得快一点,不需要重新加载一次。
4.2 ✒️学会处理懒加载
这个人简单,只需要通过import函数去加载我们的内容就可以了!
4.3 ✒️学会处理提前公共代码
提取公共代码和第三方模块,我们不需要在多个入口打包这些重复的模块而是作为一个工包相互引用。
//缓存分组 cacheGround:{ //第三方模块 vemdor: { name:'vebdor', //chunk名称 priority:1,//权限更高,优先抽离 test:node_modules/, minsize:0,//大小限制 minChunks: 1 //最少服用过几次 } //公共的模块 common:{ name:'common', //chunk名称 priority:0,//权限更高,优先抽离 minsize:0,//大小限制 minChunks: 2 //最少服用过几次 } }
4.4 ✒️学会处理使用CDN加速
实现的方法:第一步是在加上publicSrc让链接丢变成CDN;第二步就是将链接都上传到CDN服务,让链接可用,那么结果打包出来的html引入链接都是加入了cdn的。
output:{ //打包代码的时候,加上hash filename:'[name].[contentHash:8].js'//name就是多个入口时的entry path:distPath, publicPath:'http://cdn.abc.com' //修改所有静态文件url的前缀 }
4.5 ✒️学会使用production
定义:自动开启代码压缩、vue和react等会自动删除调试代码、启动Tree-shaking.
实现的方法:mode:'product'就是使用了production环境。开发环境下我们引入了一些模块,模块中如果没有使用到的函数之类的耶还是会被引入的,这个开发环境下没事,但是线上希望没有用到的就不要出现了,其实只需要开启production,打包就会实现Tree-Shaking。
注意的是:ES6 module才能让tree-shaking生效,commomjs是不行的。
4.6 ✒️了解Scope Hosting
问题:我们开发的时候是分文件的,打包后第一个该函数就是main.js,第二个函数就是hello.js;这个的问题是函数越多的话,产生得1作用域就会越多,对于内存的小号和js的执行都会不友好。
我们解决的方法是:开启Scope Hosting把多个函数合并在一个函数当中。
//开启 [ (function (module,_webpack_exports_,_webpack_require_){ var hello = {'hello'}; console.log(hello); }) ]
我们这样做的好处就是代码体积更小、创建的函数作用域更少、代码可读性好。
const ModuleConcatenationPlugin = require('webpack/lib/optimize/ModuleCincatenationPlugin') module.exports = { resolve: { //计算第三方模块优先采用指向ES6模块化的语法文件 mainFields:['jsnext:main','browser','main'] }, plugins:[ //开启Scope Hosting new ModuleConcatenationPlugin{}, ] }
我们要注意的是条件是引入的第三方模块最好是采用ES6语法写的,也就是mainField当中的优先采用jsnext指向的文件名字。
五、📃babel
5.1 ✒️环境搭建和基本配置
安装插件,配置.babelrc即可。具体babel是怎么转换的主要是通过plugins里面的东西,但是全部都写的话有太多就会有presets这些预设。预设还不满足的话可以在plugins自己再添加。
5.2 ✒️babel-runtime
问题:目前使用babel-profill的话会存在污染全局环境的问题,如果是做一个独立的web系统无碍,但是如果是做一个第三方的lib则不行。
污染全局变量的意思是引入的文件都是直接去window或者对象或者数组的原型上添加或者重新定义方法。如果作为第三方库被使用的话很可能出现覆盖冲突等.
解决:那么就需要使用babel-runtime来给方法换名字加前缀什么的,安装@babelrc/runtime、@babelplugin-transform-runtime,
回到.babelc作以下配置,这个是官方的配置(默认配就行,无需管具休意思),基本意思就是启动runtime,定义版本啊,启动generator、启用ES6的模块化啊等,运行后发现确实帮我们重新取了个名字(加了下划线)就不会污染全局变量了。