前端工程化
前端工程化
less
安装
npm install -g less
编译命令:
lessc style.less style.css
less必须在服务器环境下才能用,不能运行在直接双击点开的本地html文件里,所以要装一个http-server来做测试:
npm install http-server -g
运行时加上这两句:
<link rel="stylesheet/less" type="text/css" href="./style.less" />
<script src="//cdn.jsdelivr.net/npm/[email protected]"></script>
其中第一行导入目标less,第二行的文件能把less转化为css。最后在根目录下执行http-server即可。
语法
可设置变量
@width: 10px;
@height: @width + 10px;
#header {
width: @width;
height: @height;
}
混入
可以理解为一种函数或替换:
.bordered {
border-top: dotted 1px black;
border-bottom: solid 2px black;
}
#menu a {
color: #111;
.bordered();
}
.post a {
color: red;
.bordered();
}
选择器嵌套
原本的css写法:
#header {
color: black;
}
#header .navigation {
font-size: 12px;
}
#header .logo {
width: 300px;
}
less中可以这么写:
#header {
color: black;
.navigation {
font-size: 12px;
}
.logo {
width: 300px;
}
}
还可以使⽤此⽅法将伪选择器与混入⼀同使⽤:
.clearfix {
display: block;
zoom: 1;
&:after {
content: " ";
display: block;
font-size: 0;
height: 0;
clear: both;
visibility: hidden;
}
}
其中的&
表示当前选择器的父级(.clearfix
)。它等同于如下的css:
.clearfix {
display: block; /* 确保元素以块级元素显示 */
zoom: 1; /* 触发IE6/7的hasLayout属性,解决某些IE下的显示问题 */
}
.clearfix:after {
content: " "; /* 插入一个空格作为内容 */
display: block; /* 将伪元素设为块级元素 */
font-size: 0; /* 字体大小为0避免额外空间 */
height: 0; /* 高度为0不占空间 */
clear: both; /* 清除两侧浮动(关键清除浮动语句) */
visibility: hidden; /* 隐藏伪元素但保留其布局影响 */
}
这是一个经典的清除浮动的方式,当父元素内的子元素浮动(float)时,父元素会失去高度。由于为伪元素设置了clear: both
,也就是强制它显示在所有浮动元素下方(页面上的下,不是层次上的),所以父元素的底部至少是浮动元素的底部位置+伪元素的高度,相当于被伪元素坠到浮动元素下方了。
运算
算术运算符 +、-、*、/ 可以对任何数字、颜⾊或变量进⾏运算。如果可能的话,算术运算符在加、减或⽐较之前会进⾏单位换算。计算的结果以最左侧操作数的单位类型为准。如果单位换算⽆效或失去意义,则忽略单位。⽆效的单位换算例如:px 到 cm 或 rad 到 % 的转换。
@conversion-1: 5cm + 10mm; // 6cm
@conversion-2: 2 - 3cm - 5mm; // -1.5cm
// conversion is impossible
@incompatible-units: 2 + 5px - 3cm; // 4px(2+5-3=4)
// example with variables
@base: 5%;
@filler: @base * 2; // 10%
@other: @base + @filler; // 15%
乘法不转换,直接用数值积加上左边的单位:
@base: 2cm * 3mm; // 6cm
对颜色运算:
@color: #224488 / 2; // #112244
background-color: #112244 + #111; // #223355
转义
)允许你使⽤任意字符串作为属性或变量值。任何 ~"anything"
或 ~'anything'
形式的内容都将按原样输出:
@min768: ~"(min-width: 768px)";
.element {
@media @min768 {
font-size: 1.2rem;
}
}
编译成css是:
@media (min-width: 768px) {
.element {
font-size: 1.2rem;
}
}
函数
种函数⽤于转换颜⾊、处理字符串、算术运算等:
@width: 0.5;
.class {
width: percentage(@width); // returns `50%`
}
映射
从 Less 3.5 版本开始还可以将混合和规则集作为⼀组值的映射使⽤:
#colors() {
primary: blue;
secondary: green;
}
.button {
color: #colors[primary];
border: 1px solid #colors[secondary];
}
作用域
域与 CSS 中的作⽤域⾮常类似。⾸先在本地查找变量和混合(mixins),如果找不到,则从⽗级作⽤域继承(从里往外找):
@var: red;
#page {
@var: white;
#header {
color: @var; // white
}
}
导入
less可以省略扩展名,css不行:
@import "library"; // library.less
@import "typo.css";
Sass
环境安装⽐较复杂,⽽在我们⽇后的开发过程中,是不需要这样⼀个环境的,无需安装。
变量
用$
标识:
$nav-color: #F90;
nav {
$width: 100px;
width: $width;
color: $nav-color;
}
// 编译后
nav {
width: 100px;
color: #F90;
}
选择器嵌套
#content {
article {
h1 { color: #333 }
p { margin-bottom: 1.4em }
}
aside { background-color: #EEE }
}
等同于如下css:
#content article h1 { color: #333 }
#content article p { margin-bottom: 1.4em }
#content aside { background-color: #EEE }
同样也可以用&
表示上层元素:
article a {
color: blue;
&:hover { color: red }
}
// 编译后
article a { color: blue }
article a:hover { color: red }
导入
css有⼀个特别不常⽤的特性,即 @import 规则,它允许在⼀个css⽂件中导⼊其他css⽂件。然⽽,后果是只有执⾏到 @import 时,浏览器才会去下载其他css⽂件,这导致⻚⾯加载起来特别慢。 sass也有⼀个 @import 规则,但不同的是,sass的 @import 规则在**⽣成css⽂件时就把相关⽂件导⼊进来**。这意味着所有相关的样式被归纳到了同⼀个css⽂件中,⽽⽆需发起额外的下载请求。
@import "./init.scss"
静默注释
body {
color: #333; // 这种注释在浏览器看源码时看不到,即不会出现在生成的css中
padding: 0; /* 会出现在生成的css中,浏览器检查源码能看见 */
}
混合器
类似函数替换,用@mixin
声明,@include
调用(引入)。这里的例子是添加圆角:
@mixin rounded-corners {
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
border-radius: 5px;
}
notice {
background-color: green;
border: 2px solid #00aa00;
@include rounded-corners;
}
// 编译后
.notice {
background-color: green;
border: 2px solid #00aa00; -moz-border-radius: 5px; -webkit-border-radius: 5px;
border-radius: 5px;
}
选择器继承
个通过@extend
语法实现,⼀个选择器可以继承为另⼀个选择器定义的所有样式:
.error {
border: 1px solid red;
background-color: #fdd;
}
.seriousError {
@extend .error;
border-width: 3px;
}
它和include的区别是:它后面只能写一个选择器,而include后面必须是前面定义过的mixin。
gulp
gulp是一个自动化构建工具:压缩js、css;编译less、sass等。
安装
安装gulp命令行
npm install --global gulp-cli
创建项目并初始化gulp
mkdir gulpDemo
cd gulpDemo
npm init
安装项目依赖
npm install --save-dev gulp
在项⽬的根⽬录下,创建⼀个名字为 gulpfile.js 的⽂件,就可以在里面写任务了。
压缩任务
压缩css
安装依赖:
npm install --save gulp-minify-css
任务编写:
const gulp = require("gulp");
const minifyCSS = require("gulp-minify-css");
function minCSS(cb) {
gulp.src("./src/css/*.css")
.pipe(minifyCSS())
.pipe(gulp.dest("./dist/css"));
cb();
}
gulp.task("default",minCSS);
以上代码完成了css压缩,并创建dist/css⽬录放置压缩后的css⽂件。
压缩js
安装依赖:
npm install --save gulp-uglify
任务编写:
const gulp = require("gulp");
const uglify = require('gulp-uglify');
function minJS(cb){
gulp.src("./src/js/*.js")
.pipe(uglify())
.pipe(gulp.dest("./dist/js"));
cb();
}
gulp.task("default", minJS);
合并任务
将任务函数和/或组合操作组合成更⼤的操作,这些操作将按顺序依次执⾏。合并⼀些列的操作包含:压缩、合并、重命名:
安装依赖
npm install --save rename
npm install --save concat
任务编写
const gulp = require("gulp")
const { series } = require('gulp')
const uglify = require('gulp-uglify')
const minifyCSS = require("gulp-minify-css")
const rename = require("gulp-rename")
const concat = require("gulp-concat")
function minJS(cb){
gulp.src("./src/js/*.js")
.pipe(uglify())
.pipe(gulp.dest("./dist/js"))
cb();
}
function minCSS(cb){
gulp.src("./src/css/*.css")
.pipe(minifyCSS())
.pipe(gulp.dest("./dist/css"))
cb()
}
async function finalFile(){
await gulp.src('./src/js/*.js') // 选择合并的JS
.pipe(concat("app.js")) // 合并
.pipe(gulp.dest('./dist/js')) // 输出
.pipe(rename({suffix:'.min'})) // 重命名
.pipe(uglify()) // 压缩
.pipe(gulp.dest('./dist/js'))
}
webpack
概述
Webpack 是⼀个打包模块化 JavaScript 的⼯具,在 Webpack ⾥⼀切⽂件皆模块,通过 Loader 转换⽂件,通过 Plugin 注⼊钩⼦,最后输出由多个模块组合成的⽂件。webpack负责将浏览器不能识别的文件类型、语法等转化为可识别的前端三剑客(html,css,js),并在这个过程中充当组织者与优化者的角色。
优点:
- 专注于处理模块化的项⽬,能做到开箱即⽤⼀步到位;
- 通过 Plugin 扩展,完整好⽤⼜不失灵活;
- 使⽤场景不仅限于 Web 开发;
- 社区庞⼤活跃,经常引⼊紧跟时代发展的新特性,能为⼤多数场景找到已有的开源扩展;
- 良好的开发体验。
Webpack的缺点是只能⽤于采⽤模块化开发的项⽬。
核心概念
- Entry:Webpack 执⾏构建的第⼀步将从 Entry 开始,可抽象成输⼊。
- Module:模块,在 Webpack ⾥⼀切皆模块,⼀个模块对应着⼀个⽂件。Webpack 会从配置的 Entry 开始递归找出所有依赖的模块。
- Chunk:代码块,⼀个 Chunk 由多个模块组合⽽成,⽤于代码合并与分割。
- Loader:模块转换器,⽤于把模块原内容按照需求转换成新内容。
- Plugin::扩展插件,在 Webpack 构建流程中的特定时机注⼊扩展逻辑来改变构建结果或做你想要的事情。
- Output:输出结果,在 Webpack 经过⼀系列处理并得出最终想要的代码后输出结果。
总体构建流程

当 webpack 处理应用程序时,它会递归地构建一个依赖关系图,其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。webpack 就像一条生产线,要经过一系列处理流程后才能将源文件转换成输出结果。 这条生产线上的每个处理流程的职责都是单一的,多个流程之间有存在依赖关系,只有完成当前处理后才能交给下一个流程去处理。插件就像是一个插入到生产线中的一个功能,在特定的时机对生产线上的资源做处理。
webpack
的运行流程是一个串行的过程,它的工作流程就是将各个插件串联起来,在运行过程中会广播事件,插件只需要监听它所关心的事件,就能加入到这条webpack
机制中,去改变webpack
的运作,使得整个系统扩展性良好
构建分三大步:
- 初始化流程:从配置文件和 Shell 语句中读取与合并参数,并初始化需要使用的插件和配置插件等执行环境所需要的参数;
- 编译构建流程:从 Entry 发出,针对每个 Module 串行调用对应的 Loader 去翻译文件内容,再找到该 Module 依赖的 Module,递归地进行编译处理;
- 输出流程:对编译后的 Module 组合成 Chunk,把 Chunk 转换成文件,输出到文件系统。
初始化流程
从配置文件和 Shell
语句中读取与合并参数,得出最终的参数。配置文件默认下为webpack.config.js
,也或者通过命令的形式指定配置文件,主要作用是用于激活webpack
的加载项和插件。
webpack
将 webpack.config.js
中的各个配置项拷贝到 options
对象中,并加载用户配置的 plugins
。完成上述步骤之后,则开始初始化Compiler
编译对象,该对象掌控着webpack
生命周期,不执行具体的任务,只是进行一些调度工作。
编译构建流程
根据配置中的 entry
找出所有的入口文件。初始化完成后会调用Compiler
的run
来真正启动webpack
编译构建流程,主要流程如下:
compile
开始编译make
从入口点分析模块及其依赖的模块,创建这些模块对象build-module
构建模块seal
封装构建结果emit
把各个chunk输出到结果文件
compile 编译
执行了run
方法后,首先会触发compile
,主要是构建一个Compilation
对象。该对象是编译阶段的主要执行者,主要会依次下述流程:执行模块创建、依赖收集、分块、打包等主要任务的对象。
make 编译模块
当完成了上述的compilation
对象后,就开始从Entry
入口文件开始读取,主要执行_addModuleChain()
函数。
_addModuleChain
中接收参数dependency
传入的入口依赖,使用对应的工厂函数NormalModuleFactory.create
方法生成一个空的module
对象,回调中会把此module
存入compilation.modules
对象和dependencies.module
对象中,由于是入口文件,也会存入compilation.entries
中,随后执行buildModule
进入真正的构建模块module
内容的过程。
build module 完成模块编译
这里主要调用配置的loaders
,将我们的模块转成标准的JS
模块。在用Loader
对一个模块转换完后,使用 acorn
解析转换后的内容,输出对应的抽象语法树(AST
),以方便 Webpack
后面对代码的分析。从配置的入口模块开始,分析其 AST
,当遇到require
等导入其它模块语句时,便将其加入到依赖的模块列表,同时对新找出的依赖模块递归分析,最终搞清所有模块的依赖关系。
输出流程
seal 输出资源
seal
方法主要是要生成chunks
,对chunks
进行一系列的优化操作,并生成要输出的代码。webpack
中的 chunk
,可以理解为配置在 entry
中的模块,或者是动态引入的模块。根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk
,再把每个 Chunk
转换成一个单独的文件加入到输出列表。
emit 输出完成
在 Compiler
开始生成文件前,钩子 emit
会被执行,这是我们修改最终文件的最后一个机会。
安装
全局安装
npm i -g webpack
npm i -g webpack-cli
安装到项目
npm i -D webpack
npm i -D webpack-cli
基本使用
基本项目结构:
webpack
├─ 📁dist
├─ 📁node_modules
├─ 📁public
├─ 📁src
│ ├─ show.js
│ ├─ index.js
├─ webpack.config.js
其中show.js用来更改root中的内容:
function show(content) {
window.document.getElementById('root').innerText = 'Hello,' + content;
}
module.exports = show
index.js是主入口:
const show = require('./show.js');
show('iwen');
编写webpack.config.js:
const path = require('path');
module.exports = {
entry: './src/index.js', // 主入口文件
output: { // 出口配置
filename: 'bundle.js', // 输出文件名
path: path.resolve(__dirname, './dist'), // 输出根目录
}
};
之后运行
webpack
就能看到dist下有一个bundle.js,即输出后的js,引入html后可以使用。
使用Loader
webpack不原生支持解析某些类型的文件,如果要让它能解析,就要安装对应的Loader(加载器)。比如添加一个css文件:
webpack
├─ 📁dist
├─ 📁node_modules
├─ 📁public
├─ 📁src
│ ├─ 📁assets
│ │ ├─ 📁css
│ │ │ ├─ main.css
│ ├─ show.js
│ ├─ index.js
├─ webpack.config.js
其中main.css:
#root {
text-align: center;
}
在index.js中,需要以模块方式引入这个css文件:
require('./assets/css/main.css');
const show = require('./show.js');
show('iwen');
如果用之前的config,由于没有装Loader会报错。要让webpack能解析非js文件,需要装loader:
npm i -D style-loader css-loader
并且对webpack.config.js作如下修改:
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, './dist'),
},
module: {
rules: [ // 配置处理方案
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
}
]
}
};
这里添加了一条规则,规定所有能被/\.css$/
匹配的文件都用'style-loader', 'css-loader'
处理。
为了缩短命令,可以在package.json添加:
"scripts": {
"start": "webpack --config webpack.config.js"
}
后续只需
npm start
即可用这个配置文件开始构建。
使用Plugin
分离并压缩css
Plugin(插件) 是⽤来扩展 Webpack 功能的,它通过在构建流程⾥注⼊钩⼦实现。比如,想要做到在打包后分离出css文件并压缩,就需要使用Plugin,先安装对应的Plugin:
npm install --save-dev optimize-css-assets-webpack-plugin
npm install --save-dev mini-css-extract-plugin
修改webpack.config.js:
const path = require('path');
const optimizeCss = require('optimize-css-assets-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, './dist'),
},
module: {
rules: [
{
test: /\.css$/,
use: [
{
// 把style-loader换成plugin里的loader
loader: MiniCssExtractPlugin.loader
},
"css-loader"
]
},
]
},
plugins: [ // 初始化插件
new optimizeCss(), // 压缩css
new MiniCssExtractPlugin({ // 提取css到单独文件
filename: "main.css", // 提取目标文件
chunkFilename: "[id].css" // 保留原本文件名(可不加)
}),
],
};
注意,此时css文件已经被分离,如果要看到效果,需要在html中单独引入。
分离html
这一步的目的是,让webpack自动生成index.html中的引用关系,即让它自己找到需要的js、css等。安装:
npm install -D html-webpack-plugin
在配置文件最上方引入:
const HTMLPlugin = require('html-webpack-plugin')
在plugin那块加上:
plugins: [
new HTMLPlugin({
template: './public/index.html' // 根据这个模板生成。其中不必包含文件链接
}),
]
它会把链接好的html文件生成到dist里。
使用DevServer
webpack原生提供一个devserver,即可以提供http服务来运行项目而不是本地运行。 DevServer 会启动⼀个 HTTP 服务器⽤于服务⽹⻚请求,同时会帮助启动 Webpack ,并接收 Webpack 发出的⽂件更变信号,通过 WebSocket 协议⾃动刷新⽹⻚做到实时预览。
安装与基本使用
npm install -D webpack-dev-server
增加基本配置,把以下代码块放到webpack.config.json最外层(与plugins、module等同级):
devServer: {
static: {
directory: path.join(__dirname, './dist'),
},
compress: true,
port: 9000,
hot: true, // 热更新
open: true // 自动启动浏览器
},
注意,启动服务器的根目录设在dist下,即用打包完的资源运行服务器。在package.json添加一个命令:
"dev": "webpack-dev-server --config webpack.config.js"
就可以使用
npm run server
来启动开发服务器。
webpack的更多配置
多入口
有的项目不止一个入口,比如登录页面和主页。这种情况可以按如下修改配置:
entry: {
index: "./src/index.js",
login: "./src/login/login.js"
},
// 模式区分:⽣产和开发环境
mode: 'development',
output: {
// 把所有依赖的模块合并输出到⼀个bundle.js⽂件
filename: '[name].js',
// 输出⽂件都放到dist ⽬录下
path: path.resolve(__dirname, './dist'),
}
运行后会出现两个入口:
http://localhost:9000
http://localhost:9000/login
resolve属性
Resolve 配置 Webpack 如何寻找模块所对应的⽂件。 Webpack 内置 JavaScript 模块化语法解析功能,默认会采⽤模块化标准⾥约定好的规则去寻找,但你也可以根据⾃⼰的需要修改默认的规则。
比如,可以把如下代码块加到配置文件最外层,使得webpack可以自动解析文件类型,这样一来js、json文件就不需要自己写后缀了:
resolve: {
extensions: ['.js', '.json']
}
比如
require("./css/init.css");
// 可以改成
require("./css/init");
使用es6语法
通常需要把采⽤ ES6 编写的代码转换成⽬前已经⽀持良好的 ES5 代码,这包含2件事:
1、把新的 ES6 语法⽤ ES5 实现,例如 ES6 的 class 语法⽤ ES5 的 prototype 实现;
2、给新的 API 注⼊ polyfill ,例如项⽬使⽤ fetch API 时,只有注⼊对应的 polyfill 后,才能在低版本浏览器中正常运⾏。
安装babel依赖:
npm install --save-dev @babel/core
npm install --save-dev @babel/preset-env # 转码规则集
npm install --save-dev babel-loader # babel加载器
在项目根目录添加.babelrc
:
{
presets: [
"@babel/env"
],
"plugins": []
}
在webpack.config.js中rules下面添加一条规则:
rules: [
{
test: /\.css$/,
use: [
{
// 把style-loader换成plugin里的loader
loader: MiniCssExtractPlugin.loader
},
"css-loader"
]
},
{
test: /\.js$/,
use: ['babel-loader'],
include: path.resolve(__dirname, 'src')
},
]
这样webpack在解析js文件时就会应用babel设定的解析es6规则。
加载图片
安装loader:
npm install file-loader --save-dev
增加rules:
{
test: /\.(png|jpe?g|gif)$/i,
use: [
{
loader: 'file-loader',
},
],
},
之后可以直接引入图片组件:
import myimg from "../assets/images/timg.jpg";
webpack优化
缩小文件搜索范围
优化主要针对两个过程:寻找文件,和利用loader处理。首先,可以在配置rules时限定搜索范围:
include: path.resolve(__dirname, 'src')
另外,可以区分生产与开发环境。
一般会分出三个环境:开发、生产与测试。单独设置一个webpack.production.config.js
文件,作为生产环境的配置文件。这个文件中可以删掉所有不需要的东西,并且需要做模式区分,在最外层添加一个
mode: 'production',
在package.json中添加一个命令:
"scripts": {
"build": "webpack --config webpack.production.config.js",
"dev": "webpack-dev-server --config webpack.config.js"
},
之后可以通过
npm r
npm run dev
来区分两个环境。