前端工程化
- 模块化(js模块h化,css模块化,资源的模块化)
- 组件化(复用现有的ui,结构样式,行为)
- 规范化(目录结构的划分,编码规范化,接口规范化 文档规范化,Git分支管理)
- 自动化(自动化构建,自动部署自动化测试)
webpack是什么?
webpack是一个现代JavaScript应用程序的静态模块打包工具
。当webpack处理应用程序时,它会在内部构建一个依赖图(dependency [/dɪˈpendənsi/] graph [/ɡræf/]),此依赖图会映射项目所需的每个模块,并生成一个或多个bundle包!webpack本身是基于node.js开发的!
为啥要使用webpack
- 代码转换:TypeScript编译成JavaScript、LESS/SCSS编译成CSS、ES6/7编译为ES5、虚拟DOM编译为真实的DOM等等…
- 文件优化:压缩JS、CSS、HTML代码,压缩合并图片,图片BASE64等
- 代码分割:提取多个页面的公共代码、提取首屏不需要执行部分的代码等
- 模块合并:在采用模块化的项目里会有很多个模块和文件,需要构建功能把模块分类合并成一个文件
- 自动刷新:监听本地源代码的变化,自动重新构建、刷新浏览器
- 代码校验:Eslint代码规范校验和检测、单元测试等
- 自动发布:自动构建出线上发布代码并传输给发布系统
-
缺点:webpack打包(可以看成一个平台),需要下载各种插件,插件越多,打包速度会变慢
模块化开发
模块化开发是什么?
模块化开发其实就是封装细节,提供使用接口,彼此之间互不影响,每个模块都是实现某一特定的功能,同时也需要避免全局变量的污染。一个模块就是一个实现特定功能的文件,有了模块我们就可以更方便的使用别人的代码,要用什么功能就加载什么模块。
模块化编程进化历史
- 单例设计模式
- AMD「require.js」
- CommonJS
- CMD「sea.js」
- ES6Module
1.单例设计模式
最早的模块化,但是对引入文件的顺序要求严格:比如B.js依赖于A.js,则必须先引入A.js再引入B.js
- 缺点:虽然解决了模块化开发,防止了全局变量污染,但是依赖关系把数据处理变得`异常恶心
let xxxModule = (function () {
let time = new Date();
const query = function query() {
// ...
};
const handle = function handle() {
// ...
};
// 把供其它板块调用的方法,暴露到全局对象上
//「局限:暴露的内容比较多,则还会引发全局变量冲突」
// window.query = query;
return {
query,
handle
};
})();
2.AMD[require.js]
AMD思想是在单例模式的基础上,实现了对依赖的管控。
管控模式缺点)依赖前置,在具体开发模块代码之前,先把需要的依赖处理好,再开始开发
- 用法:使用define导出,使用require导入。
//使用之前需要导入require.min.js包
//A.js
//用define定义导出的对象,变量名是A,导入的时候用A
//如果这个模块不需要导入其他模块,可以直接写函数
let A=define(function(){
let sum=function sum(...parmas){
return parmas.reduce((res,item)=>res+item);
}
return {sum};
})
//B.js
//当需要导入其他模块时,
//第一个参数传想要导入的模块名数组
//第二个参数是当前模块的函数代码
let B=define(["A"],function(A){
const average=function average(...parmas){
parmas.sort((a,b)=>a-b).pop();
parmas.shift();
return A.sum(...parmas)/parmas.length;
};
return {average} ;
})
//main.js
//配置依赖模块的导入公共路径
require.config({
baseUrl: "./lib"
})
//require用于导入,
//第一个参数是要导入的模块,不分先后顺序
//第二个参数是本身的函数
require(["A", "B"], function (A, B) {
console.log(A.sum(1, 23, 2, 4, 53, 8));
console.log(B.average(1, 23, 2, 4, 53, 8));
})
//最后只需要在html文件中带入main文件即可
3.Common.js:应用在服务器端[node]的模块化
缺点 不能在服务器端使用
应用`node/webpack`支持CommonJS规范
用法:导出用`module.exports`,导入模块用`require`
导出:可以导出函数或对象
// 直接导出一个函数
module.exports= function sum(...params){
return params.reduce((result,item,index)=>{
return result+item;
})
}
//导出一个对象
module.exports={
sum
}
- 导入:导入目标路径必须以./开头,文件后缀可以省略
//如果目标模块导出的是一个函数,则可以直接导入那个函数
let suma=require("./B.js");//xxx.js后缀可以省略
//如果目标模块导出的是一个对象,则可以直接导入这个对象
let B=require("./B");//xxx.js后缀可以省略
let {suma,sumb}=require("./B");//可以对B直接解构赋值
4. ==CMD[sea.js]==:应用在浏览器端的模块化,旨在让Common.js能够在浏览器运行,但后期被webpack干掉了
5.- ==ES6Module==:ES6新增的模块化,主要应用在浏览器端的模块化
- 优点:我们以后在浏览器端模块化开发时,都会用ES6Module开发
- 注意点:我们在html文件中导入模块时必须声明==**`type="module"`**:==`<script type="module" src="./A.js"></script>`
- 用法:用export/export default导出模块,用import导入模块,一个模块可以导入无数次
`export default导出,import导入`:export default只能用一次
//A.js
let numa="111";
//export default 写法
// export default:导出模块中的一个默认值:(可以是任何类型数据)每个模块只能用一次
//模块导出的实际是一个Module对象,我们export default的值,会存在Module对象中的default属性
export default numa;
//B.js
//导入时文件路径必须由./开头,且不能省略文件后缀.js
//导入A模块export default的值
//导入A.js中export default导出的默认值,且把值赋给num
//我们这样的写法,A模块导出的Module对象,会把它的default属性值赋值给num
import num from "./A.js";
//因为我们接收的不是module对象而是default的值,所以不能直接解构赋值,需要再通过num结构赋值
let {na,nb}=num;
`export 导出,import导入`:export能用无数次
//A.js
let numb="222";
let numc="333";
let obj1={name:"lisa",age:18}
//export 写法
//方式一:导出一个变量,需要在export后定义赋值,不能在外面定义再只传变量名
export let nume="444";
//方式二:通过花括号的,可以同时导出多个已经定义的变量
//这种导出方式实质上是给Module对象赋值,将numb/numc/obj1赋值给Module,变为Module的私有对象
export {numb,numc,obj1};
//B.js
//将A模块导出的整个Module对象导入,拿到对象后就能通过A.xxx调用其内部的属性了
import * as A from "./A.js";
console.log(A.numb);//"222"
//因为导入的是一个Module对象,所以我们可以对这个对象进行解构赋值,获取其中的属性,因为default是一个内置私有属性,所以不能导出defalut
import { numb,numc,obj1 } from "./A.js";
console.log(numb);//"222"
webpack使用
webpack主要构成是什么
- webpack是一个基于nodejs实现的平台
webpack即支持ES6Module,又支持CommonJS规范,而且两个规范可以混用
- 入口:从哪个文件开始打包编译
- 出口:打包的文件放到哪里
- mode 存在生产环境(production)和开发环境(development)
- loader:加载器基于各种加载器实现代码的编译
- plugins插件 基于插件实现打包压缩(也包含部分文件的编译)
- 优化项...
wepack如何使用?
全局 执行 webpack命令按照我们配置好的文件,实现文件的编译打包
本地 在`package.json`中的`"scripts"`属性下配置`"xxx",然后在命令窗口中执行`npm run xxx
我们需要把配置写在项目的`webpack.config.js`文件中,可配置entry/output/mode/loader/plugins/优化项.
- - ==第一步==:删除全局的webpack:`npm uninstall webpack webpack-cli -g`
- - ==第二步==:安装局部的webpack:`npm i webpack webpack-cli --save-dev`
- - ==第三步==:检查项目目录中是否存在src路径,src中是否存在index.js目录
- - ==第四步==:在`package.json`中的`"scripts"`属性下配置`"xxx":"webpack"`,"xxx"是自定义名,然后在命令窗口中执行`npm run xxx`来实现打包【使用默认打包方式:默认打包index.js ,默认生成在:dist/main.js】
- - ==第五步==:配置`webpack.config.js`,实现自定义的控制webpack打包配置
webpack的安装方式
方式一:将webpack安装到全局
- `$ npm i webpack webpack-cli -g / npm i webpack@5.49.0 webpack-cli@4.7.2 -g`
- xxx-cli 这样的模块就是命令模块,目的是使用xxx命令
- ==优点==:所有的项目不用再单独安装webpack包,都能在文件夹下的命令行窗口使用`webpack`命令执行打包
- - ==缺点==:所有项目使用的webpack版本一样,容易引起版本冲突
*方式二:想给哪个文件夹打包,就给哪个文件夹安装webpack
- npm i webpack webpack-cli// npm i webpack@5.49.0 webpack-cli@4.7.2 安装在本地的开发依赖中,是把打包编译后的结果放到生产环境中
- - ==优点==:每个项目都有自己的webpack版本号,互不影响,也是我们做项目的时候真正用的安装方式
- - ==缺点==:不能在自己的命令行窗口,用`webpack`命令执行打包,如果想用,有两种方
- 如果wepack版本在5.2以上:使用`npx webpack`不用配置命令就可以打包式:
- 让安装在本地的模块可以基于命令操作,
- 也可以在`package.json`中的`"scripts"`属性下配置`"xxx":"webpack"`,"xxx"是自定义名,然后在命令窗口中执行`npm run xxx`来实现打包
// 为防止全局安装webpack导致版本冲突,真实项目中以本地安装为主
$ npm init -y
$ npm install webpack webpack-cli --save-dev
OR
$ yarn add webpack webpack-cli -D
webpack的默认打包
webpack有默认的打包路径:我们不需要配置`webpack.config.js`,当我们执行webpack命令的时候就可以打包(默认生产环境)
- ==默认入口是==:`文件夹/src/index.js`
- ==默认出口是==:`文件夹/dist/main.js`( 按照入口中的代码和模块依赖,最后就行打包,如果没有,会自动生成)
* 默认会打包SRC目录中的JS文件(入口默认index.js)
* 打包完成的目录默认是DIST/MAIN.JS
* webpack默认支持CommonJS和ES6 Module的模块规范,依此进行依赖打包
*/
$ npx webpack
执行webpack打包命令触发的查找机制
本地项目中可以使用的命令,都按照这个文件开始执行打包编译
- ==机制==:当我们运行 npm run serve,会把webpack命令执行
- - @1 首先]到项目的node_modules中的.bin的目录中查找
- - @2找到webpack这个文件,按照这个文件开始执行打包编译
自己配置webpack打包规则
一.配置基础
1.项目根目录中新建 webpack.config.js
- 当我们执行webpack命令的时候,首先看根目录是否有有这个文件,有这个文件按照这个文件中提供的规则进行打包编译
- 如果没有这个文件按照默认的规则进行打包编译
开发环境:程序员平时写项目的环境(development)
生产环境:项目数据前端后台已经跑通,部署在服务器上之后的环境(production)
//----------------------
let path = require('path');
//基于CommonJS规范导出配置项(webpack基于node开发的)
module.exports = {
//编译模式 开发环境development(编译的文件是非压缩的) 生产环境production(编译的文件默认就是压缩的)
mode: "production",
//编译入口
entry: './src/main.js',
//编译出口
output: {
//输出的文件名 [hash]是个规则:根据代码不同生成不同的hash值
filename: 'bundle.[hash].min.js',//避免强缓存
//输出目录 需要是一个绝对路径
path: path.resolve(__dirname, 'dist')//获取当前目录的绝对路径
}
}
真实项目中,我们一般都是根据开发环境还是生产环境,让其走不同的配置项
方案一:不同环境走不同的配置文件
- 不同环境走不同的配置文件
\webpack.config.development.js(开发环境 平时开发实时更新)
webpack.config.production.js(生产环境 压缩打包部署)
-
可在package.json中配置可执行的脚本命令(区分开发环境)
//--config执行webpack找自己指定的配置文件
"scripts": {
开发环境执行 $npm run serve
"serve": "webpack --config webpack.config.development.js",
生产环境执行 $npm run build
"build": "webpack --config webpack.config.production.js"
},
方案二:相同的配置文件,只不过根据环境变量不同做一些不同的配置
1在本地安装:$ npm i cross-env --save-dev cross-env使用 - 简书
2执行不同的命令,设置不同的环境变量 然后执行webpack按照webpack.config.js这个文件进行打包
"D": "cross-env NODE_ENV=development webpack", 开发环境
"P": "cross-env NODE_ENV=production webpack" 生产环境
- .3.设置环境变量 let NODE_ENV = process.env.NODE_ENV || 'development';
- 模式:环境变量 mode: NODE_ENV,
- 根据环境变量不同走不同的规则
let path = require('path');
//获取设置的环境变量 development/production
let NODE_ENV = process.env.NODE_ENV || 'development';
//基于CommonJS规范导出配置项(webpack基于node开发的)
module.exports = {
//编译模式 开发环境development(编译的文件是非压缩的) 生产环境production(编译的文件默认就是压缩的)
mode: NODE_ENV,
//编译入口
entry: './src/main.js',
//编译出口
output: {
//输出的文件名 [hash]是个规则:根据代码不同生成不同的hash值
filename: NODE_ENV == "development" ? 'bundle.js' : "bundle.[hash].min.js",
//输出目录 需要是一个绝对路径
path: path.resolve(__dirname, 'dist')//获取当前目录的绝对路径
}
}
二 .plugin插件(压缩,打包,编译)
1.html-webpack-plugin
把src.mian.js中的代码打包编译,编译完成后导入public/index.html中
- 需要使用一个webpack中的一个插件html-webpack-plugin
- HtmlWebpackPlugin | webpack 中文网
- $ npm i html-webpack-plugin -–save-dev
使用
let path = require('path'),
HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: "development",
entry: './src/main.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
//使用插件
plugins: [
// 目的:把打包编译好的js文件,自动导入到HTML页面中,(还可以对html页面进行编译压缩)
new HtmlWebpackPlugin({
//指定自己的模板(如果不指定也会自动生成一个index.html)
template: './public/index.html',
//编译后的HTML名字
filename: 'index.html',
//导入生成的js文件,会在后面默认加上hash戳
//我们可以不用这么设置 我们完全可以在编译js的时候,把编译后的文件设置hash值
hash: true,
//对html页面代码压缩处理 //=>https://github.com/kangax/html-minifier
minify: {
collapseWhitespace: true, //去掉空格
removeComments: true,//去掉注释
removeAttributeQuotes: true,//去掉属性引号
removeEmptyAttributes: true//把空的属去掉
}
}),
]
};
2.clean-webpack-plugin
- 每一次打包的时候,之前的打包文件也在,这样不好,我们需要把之前打包的清理掉,在打包新的
- $ npm i clean-webpack-plugin –save-dev
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports={
plugins:[
new CleanWebpackPlugin()
]
};
3.mini-css-extract-plugin
- 默认的css样式会插入到html文件的`style`标签中,这个插件会将css样式抽离出来形成新的css文件,并以link的方式导入到对应的html文件中
- mini-css-extract-plugin - npm
- $ npm i mini-css-extract-plugin –save-dev
const MiniCssExtractPlugin=require('mini-css-extract-plugin');
module.exports = {
plugins: [
//=>使用插件
new MiniCssExtractPlugin({
//=>设置编译后的文件名字
filename: 'main.[hash].css'
})
],
module: {
rules: [{
test: /\.(css|less)$/,
use: [
// "style-loader",
//=>使用插件中的LOADER代替STYLE方式
MiniCssExtractPlugin.loader,
"css-loader",
"postcss-loader",
"less-loader"
]
}]
}
}
三.本地web服务 devServer
webpack-dev-server
- 我们需要在本地创建一个web 服务,然后让服务预览我们编译后的index.html文件,自动打开浏览器,实现热更新(代码改变后自动更新)
- DevServer | webpack
- $ npm install webpack-dev-server –save-dev
webpack命令不走devserver配置 webpack server 命令才会执行对应的配置
"scripts": {
"serve": "webpack server",
"build": "webpack"
}
应用
- 这样开发,等我们的功能实现后,我们需要整体打包编译,部署到服务器上
- 把正在运行的监听服务结束 ctrl+c 在执行$npm run build打包
- 把打包的dis文件部署到服务器上即可
devServer: {
//设置协议(默认是http),域名(默认是localhost),端号
port: 3000,
host: '127.0.0.1',
//=>开启服务器的GZIP压缩
compress: true,
//=>指定访问资源的路径
static: {
directory: path.join(__dirname, 'dist'),
},
//=>自动打开浏览器
open: true,
//=>开启热更新
hot: true,
//=>开发环境下基于DEV-SERVER实现跨域代理
proxy: {
"/": {
target: "https://www.jianshu.com",
changeOrigin: true //把请求头当中的host值改成服务器地址
}
}
}
四.webpack中的加载器loader:(把less css js的语法进行编译)
1..处理css样式的
我们需要,在打包后的页面中使用样式(编译less为css文件)最后把css导入页面中
- 1.因为webpack打包的入口与是 main.js,所以我们项目中需要的资源(JS,CSS,图片...)都需要在main.js中导入
- 2.我们需要修改打包规则,让打包的时候执行less的解析
- $ npm i css-loader style-loader less less-loader autoprefixer postcss-loader –save-dev
- less& less-loader实现把less编译成css
- postcss-loader & autoprefixer:对于给一些不兼容的css3样式设置前缀的
- css-loader编译css中的@import和url这样的代码
- style-loader:把编译好的css插入到页面HEAD的style中 内嵌样式
应用
module: {
//=>配置各种加载器LOADER 加载解析顺序:(默认从右向左执行,从下向上)
rules: [
//配置less/css解析规则
{
test: /\.(css|less)$/, //=>基于正则匹配,什么样的文件执行接下来的规则
use: [//使用需要的LOADER,每一个LOADER都可以写成一个对象,进行额外的配置
"style-loader", //=>把CSS插入到HEAD中
"css-loader", //=>编译解析@import/URL()这种语法
"postcss-loader", //=>设置前缀
"less-loader",//把less编译成css
]
}]
}
postcss-loader处理起来麻烦一些
1项目目录下配置postcss.config.js
module.exports = {
plugins: [
require('autoprefixer')
]
};
2 .需要配置:期望当前项目支持那些版本浏览器
package.json-->>设置browserslist 配置兼容的浏览器列表
https://github.com/browserslist/browserslist
"browserslist": [
"> 1%",
"last 2 versions"
]
2.对js进行编译处理
Babel · The compiler for next generation JavaScript
- 处理普通语法
- $ npm i babel-loader @babel/core @babel/preset-env –save-dev
- 处理clss语法
- $npm i @babel/plugin-proposal-class-properties @babel/plugin-transform-runtime --save-dev`
- 处理 promise 等特殊语法`
- $ npm i @babel/plugin-proposal-decorators –save-dev`
- $ npm i @babel/runtime @babel/polyfill `
应用
按照package.json中配置的"browserslist"兼容浏览器,进行代码编译
module.exports = {
module: {
rules: [{
test: /\.js$/,
use: [{
loader: 'babel-loader',
options: {
//=>转换的语法预设(ES6->ES5)
presets: [
"@babel/preset-env"
],
//=>基于插件处理ES6/ES7中CLASS的特殊语法
plugins: [
["@babel/plugin-proposal-class-properties", {
"loose": true
}],
"@babel/plugin-transform-runtime"
]
}
}],
exclude: /node_modules/ //除了node_modules 都编译
},]
}
}
基于webpack配置的规则,class编译为ES5语法但是promise这种浏览器内置类无法编译
如何处理这些代码兼容?
基于 @babel/polyfill `处理ES6内置方法兼容(例如promise,abel/polyfill 自己实现了一套promise)
入口文件导入@babel/polyfill import '@babel/polyfill';
3.对图片进行编译
$ npm i file-loader url-loader html-withimg-loader –-save-dev
应用
module.exports = {
module: {
//=>模块规则:使用加载器(默认从右向左执行)
rules: [{
test: /\.(png|jpe?g|gif)$/i,
use: [{
//=>把指定大小内的图片BASE64
//=>不在指定范围的采用file-loader进行处理
loader: 'url-loader',
options: {
//把所有小于200kb的图片自动BASE64 超过200kb的图片则基于file-loader编译
limit: 200 * 1024,
//编译后输出的路径
outputPath:'/images',
//name:'[name].[ext]'
}
}],
include: path.resolve(__dirname, 'src'),
exclude: /node_modules/
}, {
test:/\.(svg|eot|ttf|woff|woff2)$/i,
use:"file-loader"
}, {
test: /\.html$/,
use: ['html-withimg-loader']
}]
}
}
五.配置优化项
optimazition**:(优化项),用来配置专门作为打包优化的插件,这样的插件存在一个特点就是功能单一且具体,就是用来优化的,所以不需要对插件做过多的配置,只需要new即可
- - `minimize`:设置是否压缩
- - `minimizer`:指定压缩的方式
1.设置优化项压缩CSS
$ npm i optimize-css-assets-webpack-plugin –save-dev
const OptimizeCssAssetsWebpackPlugin= require('optimize-css-assets-webpack-plugin');
module.exports = {
//=>设置优化项
optimization: {
//=>设置压缩方式
minimize: true,
minimizer: [
//=>压缩CSS(但是必须指定的压缩方式)
new OptimizeCssAssetsWebpackPlugin(),
]
}
};
2.设置优化项压缩JS
$ npm i terser-webpack-plugin –save-dev
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
//=>设置优化项
optimization: {
//=>设置压缩方式
minimize: true,
minimizer: [
//压缩JS
new TerserPlugin()
]
}
};
多入口 & 多出口
默认情况下,我们只能指定一个入口,并且生成一个出口文件,但是有时候我们期望输出多个出口文件,并且指定每个出口文件导入不同的模块,这时就需要我们配置了:
- `chunks`:功能是指定当前html文件需要导入哪些js文件,如果没指定的话,会默认导入所有js文件,这是不好的
//创建多个html对象,最终返回一个数据项都是html对象的数组
const htmlPlugins = ['index', 'login'].map(chunk => {
return new HtmlWebpackPlugin({
template: `./${chunk}.html`,//指定每个html入口文件名
filename: `${chunk}.html`,//指定每个html出口文件名
hash: true,//是否添加hash值,一般不用这种方法
chunks:[chunk,'jquery'],//指定当前html文件需要导入哪些js文件
//打包时对文件的进行哪方面的压缩优化
minify: {
collapseWhitespace: true,//去空格
removeComments: true,//去注释
removeAttributeQuotes: true,//去标签属性的双引号
removeEmptyAttributes: true//去标签空属性
}
});
});
module.exports={
entry: {
index: "./src/index.js",
login: "./src/login.js",
jquery:"./src/jquery.js"
},
output: {
filename: "[name].[hash].js",
path: path.resolve(__dirname, "dist")
},
plugins:[
...htmlPlugins//在这里通过展开运算符导入多个对象
]
};
总结
插件的两种配置形式?
插件其实有两种:单一功能的插件和多功能插件
- - **单一功能插件**:插件只存在优化功能,无需任何配置,这样的插件在optimization中的minimizer中new创建
- - **多功能插件**:我们可以通过配置项配置这个插件来实现不同的功能,这样的插件在plugins中new
*plugins与loader的区别?
- ==plugins:==插件是用来扩展webpack功能的,它们在整个构建过程中生效,执行相关任务
- - ==loaders:==加载器是在打包构建过程中,用来处理源文件的(JSX/less/png),一次处理一个文件
- - webpack只认识js文件,所以其他格式的文件它无法直接打包,而loader就是用来将我们上传的不同格式文件通过加载器将它**编译**为webpack认识的格式。
- - plugin并不直接操作单个文件,它直接对整个构建过程起作用
---------------------------------------分割线------------------------------------------------------------------------
真实项目中,我们需要基于webpack实现工程化,自动化(把资源自动进行打包编译处理)可以各个模块打包在一起,我们可以手动的自己设置打包规则(太麻烦了)
vuereact给我们提供了脚手架,脚手架其实就是把需要配置的webpack规则和需要依赖的模块,全部安装好了,无需自己手动进行配置,基于脚手架创建的项目,一切都准备好了,我们最多就是在别人写好的基础上改一些自己需要的配置