为什么有webpack?
web1.0阶段,还没有明确前端岗位,主要职责是编写静态页面,用Js来进行表单验证或动画效果。为了在页面上动态填充数据,后面也出现了php、jsp这种开发模式。
web2.0阶段,伴随ajax的诞生,不止负责展示界面,还能管理数据、和用户进行交互,这就诞生了jquery这样的前端库。
web3.0阶段,大前端开发/现代前端开发,前端工作变得多样化和复杂化,不止pc端、移动端、小程序这样的开发。事情多,流程复杂,就会出现问题。比如当前用模块开发,但是不同浏览器对模块化的支持不一样,而且模块化本身存在多种规范。
同时在编码中为了提高开发效率,还会使用es6+、ts、less\sass来编写,这些浏览器无法处理。
之后在整个开发过程中,我们希望在文件中实现实时监听,当内容出现修改和变更后,可以第一时间在浏览器看到修改后的内容。
在代码部署之前,对资源进行合并、压缩、优化处理。
所以我们需要webpack实现项目工程化。
webpack功能
打包:将不同类型的资源按模块处理,并打包
静态:打包后最终产出静态资源。
模块:webpack支持不同规范的模块化开发。比如es6 module,commonJS
为什么需要loader
在使用webpack中,它不是所有文件都能当模块来使用。所以需要loader进行转换,推荐用配置文件方式。
注意:loader从右到左,从上往下的匹配
browserslist
可以理解成去"Can I use" usage table网站上找到符合条件的浏览器版本信息,然后后续根据这些内容做兼容。
方法一:在src下建一个browserslistrc文件,把命令输入进去
方法二:在package.json里面写browserslist配置
然后输入npx browserslist就可以看到结果了:
postcss
postcss是什么?
利用js转换css样式的工具。主要用这个工具做一些兼容性处理。
安装postcss-cli的目的:为了在终端能使用postcss
autoprefixer可以对样式属性做一个前缀的添加(需要安装)
eg:输入
npx postcss --use autoprefixer -o ret.css ./src/css/test.css
顺序问题:
从右往左分别是 less-loader、postcss-loader、css-loader、style-loader
postcss-preset-env是什么?
预设,看做一个插件集合。(autoprefixer包含在postcss-preset-env里)
用法:比如把#12345678(前6位是颜色,7 8位是透明度)做一个兼容
先安装,然后plugins里引入,然后就会把这样的color
兼容成rgba的颜色
importLoaders属性
例子:在login.css中引入test.css
npm run build
发现只有color做了兼容,引入的test.css没有做兼容。
原因:
login.css被匹配到之后postcss-loader进行工作,基于上面代码,postcss-loader拿到login.css代码之后,将代码交给css-loader。css-loader可以处理@import /url 语句,这时候css-loader拿到test.css文件,但不会回头去找postcss-loader 了。所以test.css里的代码没有兼容
解决办法:
file-loader
把图片看成一个模块打包进来的工具。
首先图片可能通过这两个属性设置:
(1)img 的src
webpack5之前使用require的时候,会直接把require的资源返回(图片路径/名称),5之后默认返回的是个对象,里面有default属性,default属性值才是我们想要的。
(1)想使用的话就加个default
(2)或者配置一下
(3)采用esModule的导入规范
(2)background 的url
但是发现并不能正确展示,因为login.less文件会被匹配到这里
而css-loader会识别url,对于这行代码:
css-loader看到url后会自动调换为require语法,require语法会默认导出一个esModule,解法就同上了
但是因为这里是样式文件,没法直接这样写:url(...).default
方法:不让css-loader用esModule
设置图片名称
我们期望打包后文件的名称能够按我们自己定的规则来显示:
options里面可以配置name属性,name里放的是占位符。
常见占位符:
[ext]:处理扩展名
[name]:处理文件名
[hash]:当出现文件同名的情况,hash方式组合成文件内容
[contentHash]:在file-loader里使用hash和contentHash结果不一样
[hash:<length(当前保留长度)>]:想将hash变得更短一些
规定打包路径可以用outputPath,也可以简写写到name里:
打包后
url-loader
把上面配置的file-loader改成url-loader:
url-loader和file-loader区别
file-loader会把当年图片路径返回,然后把要打包的图片资源拷贝到路径。
url-loader,默认情况下会把当前要打包的资源以base64url方式加载到代码当中。
优点:file-loader会把资源拷贝到指定的目录,访问静态资源会分开请求(比如2张图片,就要请求2次),现在只需要main.js来把所有资源都返回(请求1次),减少请求次数。
缺点:当图片很大时,相当于我们一次请求的数据量会很大,如果是首屏加载,请求时间就会更长。
url-loader内部也可以调用file-loader,通过limit属性
如果图片大小大于1024*25,那就通过拷贝的方式,如果没有超过和这个值那就通过base64 url方式展示。
const path = require('path');
module.exports = {
entry: './src/main.js',
output: {
filename: 'build.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [ //webpack默认情况下能处理js和json,其他类型都不认识,需要单独配置
{
test: /\.css|.less$/, //一般是一个正则表达式,用来匹配我们要处理的文件类型
// use:[
// {
// loader:'css-loader',
// options:''
// }
// ]
// loader:'css-loader' // 如果只有一个loader,且这个loader没有什么配置参数,就可以这样简写
use: ['style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 1, //css-loader在工作中会往前找前1个loader处理
esModule: false
}
},
// {
// loader: 'postcss-loader', // 相当于在postcss-loader里面调用postcss,postcss又提供插件autoprefixer
// options: {
// postcssOptions: {
// plugins: ['postcss-preset-env'] // require('postcss-preset-env')
// }
// }
// },
'postcss-loader', 'less-loader']
},
{
test: /\.(gif|jpe?g|png|svg)$/,
use: [
{
loader: 'url-loader',
options: {
// esModule: false //不转为esModule
name: 'img/[name].[hash:6].[ext]',
// outputPath: 'img',
limit: 1024 * 25
}
}
]
}
]
}
}
assets
assets/resource:和file-loader功能一样:拷贝到目录
我们发现最终打包会变成两个“.”,就是配置assetModuleFilename的时候webpack会在扩展名前自动加个“.”,所以我们不需要自己加。
assets/inline:把行内资源添加到当前代码里,相当于url-loader
assets/source:相当于raw-loader
asset模块处理图标字体
比如这样的图标样式文件
webpack插件plugin
如果配置文件当中出现配置项更改,就需要重新打包,手动到dist目录下删除,然后再重新执行打包;从发布角度来说,最终会产出静态目录,比如css、img、js、index.html这种目录文件,现在我们每次打包完成都需要手动到index.html里面手动进行修改。这些操作就可以交给插件来做。
当我们有了插件之后为什么还用loader?
因为我们总觉得loader能干的事情插件也能干。
对于loader来说就是对特定的模块类型进行转换。比如webpack默认情况下可以识别js、json,但当处理图标、字体样式等时,不认识,没法当成模块对待,所以就需要webpack将这些非模块转为模块能处理的内容。工作时机:当读取文件内容就进行工作了。
插件:能做更多的事情,打包整个过程就是webpack的生命周期,它能在任意时机插进来。它也能在打包过程做优化,比如对css做压缩处理;还可以定义全局变量,让整个项目在不同文件做数据共享。
clean-webpack-plugin
场景:配置文件当中出现配置项更改,就需要重新打包,手动到dist目录下删除,然后再重新执行打包。自动化这个删除dist目录的操作。
html-webpack-plugin
场景:打包后产出的index.html文件,比如title、引入的src文件等都需要手动到index.html里面进行修改。
(这里为什么CleanWebpackPlugin需要解构,HtmlWebpackPlugin不需要解构是因为他们作者的导出方式不一样)
执行npm run build
发现title是Webpack App,然后进入npm官网查看参数
传参:
copy-webpack-plugin
场景:把a拷贝到b位置。
但是如果拷贝的内容有重复的话会报错:
这时候就需要:(**/是格式写法,**表示当前from路径)
define-plugin
define-plugin是webpack自带的一个plugin,不用安装。
如果我们想自定义配置一套html模板。
先创建public目录,public目录里存放的一般是直接copy的不需要webpack打包的,但是后期发布时候需要的资源。
define-plugin是用来设置这个BASE_URL的
打包后:
babel
为什么需要babel?
比如当前项目用的react,react会去写jsx语法,或者用到ts。这些对于浏览器来说都不能直接识别,但是开发时要用。所以中间就需要有个转换的过程。
然后我们发现,默认情况下,打包之后不会对它进行任何处理
先把babel/core安装上,(babel核心)
它也同样不能在命令行中使用。还需要安装@babel-cli才可以在命令行(npx babel这种方式)中直接使用。
输入命令行 npx babel src --out-dir build
发现虽然有build文件夹了但是main.js并没有做转换。因为修改这个操作还需要安装对应的babel。
@babel/plugin-transform-arrow-functions:将箭头函数转换成普通函数
@babel/plugin-transform-block-scoping:处理跨作用域的,把let/const处理成var
命令行:npx babel src --out-dir build --plugins=@babel/plugin-transform-arrow-functions,@babel/plugin-transform-block-scoping
但是一个一个这样输入太麻烦了,babel也有预设。
npm i @babel/preset-env -D
npx babel src --out-dir build --presets=@babel/preset-env
babel-loader
配置方式:
(1)webpack
注意点:如果同时有browserlistrc文件和targets属性,生效的是targets属性
(2)单独拎出来,babel.config.js(推荐)
polyfill
webpack5基于优化打包速度的考虑,polyfill被移除掉了,用到的时候需要自己安装。(webpack4默认自动带上,webpack5可按需安装)。
使用场景:
preset-env无法把所有语法都转到浏览器平台做兼容,我们需要polyfill打补丁。
安装:npm i @babel/polyfill --save
polyfill拆分成:
core-js/stable:让babel-loader把ECMAScript核心功能填充进来。
regenerator-runtime/runtime:当需要转换generator function的时候,就可以用这个。
babel的配置:这里corejs默认用2,如果安装的是3需要写一下corejs属性。
注意点:
在开发中用到的第三方的包中也会用到需要polyfill的东西,如果同样的地方,第三方包polyfill了,我的代码也polyfill了,在浏览器环境下就可能出现问题。所以一般需要把node_modules里的东西去除掉,不用babel。
webpack-dev-server
方法一:watch+live server的搭配:
不足:1.编译是所有源代码都会编译。2.每次编译成功后都要进行文件读写。(性能消耗大)3.live server是vscode下的插件。4.不能实现局部刷新
方法二:webpack-dev-serve:
所有数据在内存中完成;局部更新;
方法三:webpack-dev-middleware(支持定制化操作)
webpack-dev-middleware是一个容器,它可以把webpack处理后的文件传递给服务器。
需要安装express和webpack-dev-middleware
HMR
理解:局部动态刷新
但是加了这个还是会刷新整个页面。
可通过这里加入指定文件名,控制具体文件热更新。
React组件支持热更新
需要安装:
@babel/core
@babel/preset-react(转换jsx语法用到的插件)
@pmmmwh/react-refresh-webpack-plugin
react-refresh
Vue组件热更新
安装:
vue-template-complier
vue-loader@14:在15版本之前不需要处理plugin,之后需要手动加载插件。
publicPath
output下的publicPath默认是空字符串,告诉index.html内部的引用路径(需要加载的资源)。
规则:域名+publicPath+filename来进行访问。
devServer下的publicPath,用来指定本地服务所在的目录。(个人感觉就像是给本机该项目目录重命名。) contentBase,场景:想访问的资源不经过webpack处理。为打包后的资源(index.html)依赖了其他资源(utils.js),告诉它去哪找其他资源。
watchContentBase:和contentBase配套。作用:当utils内容变化,页面会热更新。
hot:'only':我们不希望当前页面某个组件被另外组件(某个组件依赖另一个组件)受影响。比如A组件里用到了B组件,但是B组件报错了,导致A组件无法正常显示。现在设置hot后,B报错但不影响A组件的渲染。
compress:true。开启gzip压缩。
开启gzip压缩之后:
historyApiFallback:true。作用:将404页面自动跳转为index.html
proxy:(当index.html需要用到其他数据,然后这些数据在另外的端口上(跨域时))
例子:
resolve模块解析规则
(1)先确定路径,如果是结对路径就不用再做处理了,
如果是相对路径,基于当前文件上下文进行查找
如果是模块就直接找node_modules了。
(2)判断文件还是文件夹。如果是文件,看有没有明确后缀,没有就去找resolve下的extensions配置。如果是文件夹,进行内容补全,默认是index.js
mode模式
默认为production,production | development
production会对源码做一些丑化(比如源代码写的fn函数,webpack为了更好处理,给它变成别的字母)和优化(把多余的空格换行注释都去掉)。
development对于阅读会友好一点,会自动把webpack的devtool改成evel。
devtool
用来控制是否生成source-map,以及如何生成source-map。
当mode设置为production后,devtool就被默认添加为evel了。通过sourceURL就能定位到源代码。
source-map: 会生成.map文件。
evel-source-map: 不生成.map文件。每个模块都有对应的sourceURL保存起来,并且是以base64字符串方式塞到sourceURL。
inline-source-map:在打包后的main.js最后有sourceURL,并且是以base64字符串方式塞到sourceURL。
cheap-source-map:在报错后,只提供行信息,不提供列信息;且内容是编译后的信息。
cheap-module-source-map:源代码还原出来了;只提供行,不提供列信息。
hidden-source-map:不能直接定位源代码,定位到打包后的文件。
nosources-source-map:有.map文件;能提示出来文件的哪行报错,但是点击报错信息无法定位到具体哪行那列。
source-map比下面evel/inline-souce-map,会多发起一次请求(就是.map文件)
发布阶段不推荐用souce-map,因为会暴露源代码。
测试阶段希望很快定位到bug位置,推荐cheap-module-source-map。
source-map
为什么需要source-map?
运行在浏览器端的代码和我们编译过的代码有差异,因为经过webpack处理可能变成压缩后的一行代码。如果报错,定位不到对应哪行代码导致的错误,调错很困难,所以需要source-map。
source-map是什么?
一种映射的技术,可以把转换后的代码,给他映射成转换前的代码。这样就能定位到错误信息了。
好处:在调试时可以定位到源代码中的错误信息。
main.js.map
version:souce-map版本
file:打包后的文件
mappings:算法,映射关系。
sources:资源,告诉将来map文件通过哪些文件转换来的
sourcesContent:对源代码进行备份
names:对这里的字符进行特殊处理(比如替换操作)
sourceRoot:当前souce-map文件的根路径。
ts-loader
和less-loader有点像,依赖typescript,需要安装typescript。
只提供语法转换,不支持新的特性。
就需要用到babel-loader来兼容新特性了。
@babel/preset-typescript
如果用babel-loader来处理ts,当ts文件有语法错误时,这个错误在编译阶段没法暴露出来,只有运行代码时候才能看到错误。不影响打包操作。
而用ts-loader,在做build操作后就会暴露出来错误。
加载Vue文件
安装vue、vue-loader、vue-templete-complier
const VueLoaderPlugin=require('vue-loader/lib/plugin');
...
...
new VueLoaderPlugin()