手写系列之React-redux

痛定思痛,悲痛难已,放弃源码,后补webpack,决定先做手写系列内容。主要最近被问到哑口,各种语塞,是时候搞一波创造了

** 很多都是自己的理解,”谨”供参考

单向数据流

 React是单向数据流,首先我们讲讲单向数据流
 何为单向数据流,顾名思义,就是数据像河流一样只能从一端流向另一端,而且是地势较高的一端(顶级/父级)-向地势低的一端(子级)流动
 这种数据传输特点保证了数据的纯净性,当我们想”追根溯源”,可以轻易检测到问题症结
 当然,React或Vue都可以通过$emit,使子组件通知父组件变更数据,但它依然不会破坏数据的单一流动

Content

 基于单向数据流的处理方式,使我们想要从顶层将数据传给底层子组件,需要中间跨越层层组件
 假使我们嵌套了N层,意味着我们的数据需要经过N层组件,中间组件N-2个组件都是毫无意义的数据传声筒
 而这样的情况有很多,像我们的用户数据,公共配置…等等
 Content就是帮我们做全局数据处理的,我们常用的React-redux/React-router都是基于Content
 还记得redux是怎么”劝”我们的嘛?

    - “如果你不知道是否需要 Redux,那就是不需要它。”
    - “只有遇到 React 实在解决不了的问题,你才需要 Redux 。”

 redux解决了我们的公共数据问题,但redux只是工具,没有redux我们一样可以实现功能
 是的没错,就是自己使用Context实现

 我们先来看语法

  • Content创造者: 一般在放在根组件
  • Content消费者: 任何需要使用Content的组件
1
2
3

// 创建一个 Context 对象
const MyContext = React.createContent(defaultValue);

前端常用学习网站

@(welcome)[前端菜鸟|掘金中]

###技术网站

###资源整合

###美工集锦

###全栈修炼

###官方文档

###前端工具

~~~~

###JS库

###JS规范

###jQuery插件及特效

###插件库

###H5 和 CSS3

###代码优化

###快速搭建项目

###深度好文

###前端搭建工具

###前端服务器

###待学习

###关于工具

###关于mac

###编辑软件

###前后端通道

###company

###微信小程序

###兼职orwork

###后端步步为营

上古利器jQuery源码剖析(一)

上古利器 虽经沧海变革 宝剑尚锋

 随着数据驱动时代的到来,jQuery不可避免的跌下神坛,但应记得它曾何等辉煌
 我们学习jQuery,不是学习它的用法,而是学习它的架构,它的思想,来深刻理解JS
 优秀的jQuery还未落伍,像当下最火着的axios/fetch等都源自对ajax的吸纳,11年就出现的异步解决方法deferred,在Promise里处处是它的影子
 之前并没有怎么看过别人的源码教程,第一次写源码稍显吃力,给自己加油吧

  * 首先,你应该知道

 $ 只是一个 jQuery的一个别名
 jQuery是一个构造函数,可以理解为就是一个普通的构造函数
 但是我们在调用jQuery的时候,并没有使用new关键字呢.其实这里,jQuery帮我们做了一层语法糖的封装,即调用$()的时候,它自动帮我们创建了jQuery的实例对象
 即$()时就为我们实现了new

  * 经典的jQuery框架

 首先我们创造一个自执行函数,传入this,通过root来接收
 在匿名函数里 我们创建一个叫jQuery的变量,jQuery变量拿到的是一个匿名函数的引用
 这个匿名函数,我们会把它当成构造函数
 因为我们是在全局环境下传入的this,所以这里的this指的就是window对象
 给环境变量root扩展一个叫做jQuery的属性,让它指向jQuery变量,同时再给root扩展一个叫做$的别名
 在随手给jQuery设置一下原型方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(function(root){
var jQuery = function () { // 构造函数

};

jQuery.prototype = {
init: function() {

}
};

//root.jQuery = jQuery;
root.$ = root.jQuery = jQuery;
})(this); // 环境变量this 因为是全局环境 所以这里指的是window

 上面的代码看起来一点都不复杂,甚至还有一点小简单,但是别忘了,我们刚才说过jQuery是一个构造函数,$()时为我们实现了new
 让jQuery返回new jQuery(),就可以完美解决!图样图破森,那样会死循环
 我们再造一个构造函数,让jQuery返回这个构造函数不就好了,这个构造函数就是init

1
2
3
4
5
6
7
8
9
10
11
12
13
(function(root){
var jQuery = function () { // 别忘了 jQuery我们也是当做构造函数的
return new jQuery.prototype.init(); // 返回构造函数init的实例对象
};

jQuery.prototype = {
init: function(){ // init是一个匿名对象,在这里我们将它当做一个构造函数

},
};

root.$ = root.jQuery = jQuery;
})(this);

  * 共享原型

 新的问题来了,$()返回了init方法,而init方法的原型对象上什么都没有,如何将方法扩展进来呢?
 如果init的原型对象是jQuery.prototype,那jQuery.prototype的所有方法都会挂载到init的实例中,也就是$()中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(function(root){
var jQuery = function () { // 别忘了 jQuery我们也是当做构造函数的
return new jQuery.prototype.init(); // 返回构造函数init的实例对象
};

jQuery.prototype = {
init: function(){ // init是一个匿名对象,在这里我们将它当做一个构造函数

},
};

jQuery.prototype.init.prototype = jQuery.prototype; // init的原型对象指向为jQuery的原型原型对象

root.$ = root.jQuery = jQuery;
})(this);

 init是jQuery.prototype的一个方法,init的原型对象又是jQuery.prototype,那么init拥有了jQuery.prototype的所有方法(包括它自己)
 设计的思路是这样的,我们有两个函数jQuery和init,$()就等同于实例化jQuery即new jQuery(),但们无法直接让jQuery函数返回new jQuery,这样会形成死循环,于是我们实例化了另一个构造函数init
 我们将init方法的原型也指向了jQuery的方法原型,实现了原型共享
 因此,jQuery.prototype原型对象里封装的所有属性和方法,jQuery与init都是可以调用的
 最反思维的地方就在于了,init是Query.prototype原型对象的一个方法,可以想想计算机的自举~

* 工具函数

 jQuery框架非常重要的一个函数-工具函数,它的作用是将jQuery所有的自身对象或者实例对象的属性和方法,全部都剥离开来,进行分类管理
 jQuery中一共有两个对象,一个是实例对象,一个是jQuery本身的对象
 起手式中我们实现了实例对象,而jQuery除了是一个构造函数,它也是一个函数对象,我们也有很多函数对象是挂载到jQuery本身上的,例如$.ajax/$.each等
 我们给jQuery添加一个静态属性fn,我们让fn等于jQuery的原型对象,这样我们在外部扩展时,至需要扩展jQuery.fn即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(function(root){
var jQuery = function () {
return new jQuery.prototype.init();
};

jQuery.fn = jQuery.prototype = { // jQuery的静态方法等于jQuery的原型对象,原型对象也是对象
init: function(){},
};

jQuery.fn.init.prototype = jQuery.fn; // 替换

root.$ = root.jQuery = jQuery;

})(this);

 外部扩展时,只需要$.fn.新方法名 = function(){} ->插件编写的形式
 通过调用静态属性扩展到jQuery的实例对象上

* extend

 extend在jQuery函数内部是作为一个工具函数存在,用它来管理不同对象(jQuery本身以及它的原型对象)的扩展
 extend在外部可以给 ①任意对象扩展/浅拷贝/深拷贝 ②jQuery本身扩展 ③jQuery实例对象扩展

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
(function(root){
var jQuery = function () {
return new jQuery.prototype.init();
};
jQuery.fn = jQuery.prototype = {
init: function(){},
};


// jQuery与 jQuery.prototype的原型都扩展了一个extend属性
jQuery.extend = jQuery.prototype.extend = function () {
// 方法调用时,我们有可能给任何对象/jQuery对象/jQuery原型对象进行扩展
// 我们通过参数来进行区分
// 参数1 是我们要扩展的对象 参数2 是我们要变量的对象
// 分析 有1个参数 可能给jQuery对象/jQuery原型对象扩展 有2个及以上 给任意对象arguments[0]扩展

var target = arguments[0] || {}; // 参数处理: 参数为空? 赋值对象不是引用类型?
var length = arguments.length; // 参数个数
var i = 1; // 默认参数个数
var option;
var name;

if(typeof target !== "object"){
target = {};
}

// 1个参数
if(length == i){

}

// 2个及以上参数
for(;i<length;i++){
if((option = arguments[i]) !== null){
for(name in option){

}
}
}
};



jQuery.fn.init.prototype = jQuery.fn;
root.$ = root.jQuery = jQuery;

})(this);



从0基础到大牛-webpack深入浅出超强完整版(四)

你可能只需要这一个教程

webpack - 昆仑山上光明顶

  loader和plugin都是webpack重要的组成部分,loader主要用于文件内的处理,而plugin主要用于文件间的处理,有时他们需要共同协作
  plugin的执行顺序与书写顺序无关,它通常是一个构造函数,挂载到webpack不同的生命周期中,等待执行

  • html-webpack-plugin

  html-webpack-plugin是我们最长的Plugin之一,它会帮我们管理html,
  下载插件npm install --save--dev html-webpack-plugin,配置如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {},
output: {},
mode: "development",
module:{},
plugins: [

// new HtmlWebpackPlugin()
// 默认在output文件下生成一个标题为Webpack App的html文件,并引入entry内的所有文件

new HtmlWebpackPlugin({
template : "./src/index.html"
})
// 指定html模板文件 会自动复制当前文件到output文件下,并引入entry内的所有口文件
]
};

  • clean-webpack-plugin

  想要每次新打包前清除output文件,以防止废弃文件占用内存空间,我们可以使用clean-webpack-plugin
  下载插件npm install --save--dev clean-webpack-plugin,配置如下

1
2
3
4
5
6
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
plugins: [
new CleanWebpackPlugin(),
]
};

  • 指定服务器地址

  如有需要指定打包的服务器地址,可配置如下

1
2
3
4
5
6
7
module.exports = {
output: {
filename: '[name].js',
path: path.resolve(__dirname,'dist'),
publicPath: "http://xieyuxuan.cc" // 服务器地址
}
};

  打包后html引入的入口文件,都将会是publicPath的值+文件名

  • 错误调试 - sourceMap

  早期webpack打包后如有代码错误无法直接定位到源码,仅显示打包后的文件错误位置,这给开发调试造成了很大障碍
  sourceMap是一个映射关系表,能够将我们所有的源文件的映射到打包后的文件上。
  通过配置devtool,满足不同场景的调试需求
js module.exports = { devtool: 'none' // 默认值是none // source-map 会在output文件下生成一个 [output.filename].map的 映射文件 // inlne-source-map 将映射文件打入[output.filename] 内最底部 // cheap-module-source-map 生成环境中最常使用 生成[output.filename].map的 映射文件 // cheap-module-eval-source-map 开发环境中最常使用 // ... };

  • webpack-dev-server - 本地服务器

  webpack –watch命令可以实现文件更新检测,但这种方式无法满足我们的开发需要
  webpack-dev-server主要用于本地开发,满足我们的常规开发需求,大大提升我们工作效率
  它为我们开启一个本地服务器,可以实现热更新、跨域(服务器端没有跨域)、重定向等功能
  下载webpack-dev-server依赖npm install --save--dev webpack-dev-server,并在webpack.config.js中配置devServer属性

1
2
3
4
5
6
7
8
9
module.exports = {
entry: {},
output: {},
mode: "development",
module:{},
devServer:{
contentBase: './dist', // 服务器根文件地址
}
};

  在package.json中添加脚本,实现npm run dev开启本地服务

1
2
3
4
5
6
{
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "webpack-dev-server"
}
}

  终端执行npm run dev后,webpack会告诉我们webpack-dev-server为我们开启了一个地址为http://localhost:8080的服务
  此时观察dist文件,该目录下文件清空,但页面显示正常,文件更改后会自动刷新页面并重新执行
  dist下的文件不再存放在磁盘中,而是被放置在内存中

  webpack-dev-server其它常用配置如下

1
2
3
4
5
6
7
8
module.exports = {
devServer:{
contentBase: './dist', // 服务器根文件地址
open: true, // 自动打开页面
host: 'localhost', // 指定服务器地址 默认 localhost
port: 8080 // 指定端口号 默认8080
}
};

  实现HMR(热模块更新)

1
2
3
4
5
6
7
8
9
10
11
12
const webpack = require('webpack');

module.exports = {
devServer:{
contentBase: './dist',
open: true,
hot: true // 开启热模块更新
},
plugins: [
new webpack.HotModuleReplacementPlugin() // 配置热更新
]
};

  • TreeShaking

  有时我们定义了N个方法,但仅有部分方法被调用,又不想让所有的方法都被打入,这时候我们就需要shaking一下
  将未使用的 exported member 标记为 unused 同时不再导出
  这里必须使用ESmodule的引入方式
  webpack.config.js新增配置如下

1
2
3
4
5
6
7
8
module.exports = {
entry: {},
output: {},
mode: "development",
optimization: {
usedExports: true, // 开去tree shaking
},
};

  同时需要在package中新增配置,取消副作用

1
2
3
4
5
{
"name": "webpack-demo",
"version": "1.0.0",
"slideEffects": false
}

  虽然webpack设置了tree shaking,但是比如包中有一些修改、设置、日志等产生的副作用,依然会被打入
  如果你不需要这些副作用,可以设置slideEffects为false,或者通过数组指定
  我们在设置副作用时应当明确,这个包没有副作用或者不需要它的副作用
  webpack会在线上shaking掉,开发环境制作包分析

  • 开发模式 和 生产模式

  开发模式 development 本地编写代码
  生产模式 production 用于打包完成后交给线上服务
  区分两种模式,实现不同环境的打包,现我们新建webpack.dev.js做开发配置,并将所有开发环境的配置保留,可参考如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const webpack = require('webpack');


module.exports = {
entry: { 'app' : './src/app.js' },
output: {
filename: '[name].js',
path: path.resolve(__dirname,'../dist'),
},
devtool: 'cheap-module-eval-source-map',
mode: "development",
devServer:{
contentBase: './dist',
hot: true,
},
optimization:{
usedExports: true,
},
module:{}, // 此处省略
plugins: [
new HtmlWebpackPlugin(
{template : "./src/index.html"}
),
new CleanWebpackPlugin(),
new webpack.HotModuleReplacementPlugin()
]
};

  新建webpack.prod.js,做线上配置,并将所有线上环境的配置保留,可参考如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
entry: {'main' : './src/main.js'},
output: {
filename: '[name].js',
path: path.resolve(__dirname,'../dist'),
},
devtool: 'cheap-eval-source-map',
mode: "production",
optimization:{
usedExports: true
},
module:{}, // 此处省略
plugins: [
new HtmlWebpackPlugin(
{template : "./src/index.html"}
),
new CleanWebpackPlugin(),
]
};

  将webpack配置文件都放入根目录下的config文件夹内
  配置package.json,通过不同的命令执行不同的打包环境

1
2
3
4
5
6
7
8
{
"name": "webpack-demo",
"version": "1.0.0",
"scripts": {
"dev": "webpack-dev-server --config config/webpack.dev.js",
"build": "webpack --config config/webpack.prod.js"
}
}

  通过命令npm run dev与命令npm run build我们实现了不同环境下的打包
  我们已经可以区分环境做处理了,下一步我们进行代码优化,避免重复定义
  新建webpack.common.js做公共配置文件,并将两种环境下共同部分迁移进来,可参考如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
entry: { 'app' : './src/app.js' },
output: {
filename: '[name].js',
path: path.resolve(__dirname,'../dist'),
},
optimization:{
usedExports: true,
},
module:{}, // 此处省略
plugins: [
new HtmlWebpackPlugin(
{template : "./src/index.html"}
),
new CleanWebpackPlugin(),
]
};

  修改环境配置项,仅保留公共配置不包含的,可参考如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// webpack.dev.js

const webpack = require('webpack');
module.exports = {
devtool: 'cheap-module-eval-source-map',
mode: "development",
devServer:{
contentBase: './dist',
hot: true,
},
plugins: [
new webpack.HotModuleReplacementPlugin()
]
};


// webpack.prod.js
module.exports = {
devtool: 'cheap-eval-source-map',
mode: "production",
};

  如何将配置文件合并呢,我们需要webpack-merge,下载依赖npm install --save--dev webpack-merge
  通过webpack-merge返回的方法实现合并(类似于Object.assign,实现对象的合并),如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// webpack.dev.js
const webpack = require('webpack');
const merge = require('webpack-merge');
const commConfig = require('./webpack.common.js');

const devConfig = {
devtool: 'cheap-module-eval-source-map',
mode: "development",
devServer:{
contentBase: './dist',
hot: true,
},
plugins: [
new webpack.HotModuleReplacementPlugin()
]
};


module.exports = merge(commConfig,devConfig);

// webpack.prod.js

const merge = require('webpack-merge');
const commConfig = require('./webpack.common.js');

const prodConfig = {
devtool: 'cheap-eval-source-map',
mode: "production",
}

module.exports = merge(commConfig,prodConfig);

从0基础到大牛-webpack深入浅出超强完整版(三)

你可能只需要这一个教程

webpack - 万物皆可打包

  • 打包CSS

  通过import或require引入样式文件,webpack无法直接打包,需引入css-loader将其转化
  下载依赖css-loader与style-loadernpm install --save-dev css-loader style-loader,并新增配置如下

1
2
3
4
5
6
7
8
9
10
11
12

module.exports = {
module:{
rules: [
{
test: /\.css$/,
loader: ['style-loader','css-loader'],
exclude: /node_modules/
}
]
}
};

  loader的执行顺序是从后向前,即先执行css-loader,转化后交给style-loader,将其挂载到html文件内
  css-loader负责转化css,而style-loader将转化后的css,包裹在style标签内,并将其插入到html文件的header中
  此处应注意包引入的顺序与被置入的内联样式顺序有关,重复并不会转化多次
  style-loader是以文件为单位,将其包裹为一个内部样式
  css实际打包进了js文件,并未分离出来,后面将讲解css的分离配置

  • 打包CSS预编译语言(scss/less/stylus)

  css预编译语言同样无法直接编译,需要使用相应的loader,我们以sass为例
  下载sass-loader,这里我们跟着官方文档第一个来配置
  有时官方文档也会出现更新不及时的问题,另sass-loader依赖node-sass,它在底层是用C语言编写,可能会在服务器端出现依赖包问题
  下载依赖npm install sass-loader node-sass webpack --save-dev,并配置如下

1
2
3
4
5
6
7
8
9
10
11
12

module.exports = {
module:{
rules: [
{
test: /\.s[ac]ss$/i,
loader: ['style-loader','css-loader','scss-loader'],
exclude: /node_modules/
}
]
}
};

  与css相比,我们需要先使用scss-loader将sass语言转为css语言

  • 添加CSS3前缀

  浏览器一日不统一,我们就需要一日跟兼容性抗争到底。每次写样式我们都要加一堆的兼容前缀么?自动添加兼容代码,摆脱兼容烦恼
  下载依赖npm install --save--dev postcss-loader autoprefixer
  需要在css的loader中修改,我们以sass加配置

1
2
3
4
5
6
7
8
9
10
11
module.exports = {
module:{
rules: [
{
test: /\.s[ac]ss$/i,
loader: ['style-loader','css-loader','sass-loader','postcss-loader'],
exclude: /node_modules/
}
]
}
};

  同时我们需要在根目录下新建postcss的配置文件postcss.config.js,配置如下

1
2
3
modules.exports = {
plugins: [ require("autoprefixer") ]
};

  此处也可以直接配置在webpack中,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module.exports = {
module:{
rules: [
{
test: /\.s[ac]ss$/i,
loader: ['style-loader','css-loader','sass-loader',{
loader: 'postcss-loader',
options:{
plugins: [require('autoprefixer')]
}
}],
exclude: /node_modules/
}
]
}
};

  post-loader会自动加载postcss.config.js下的配置,将文件内配置的插件加载进去
  这时再打包出来的css,将会自动为我们加入兼容前缀

  • css模块化打包

  当前引入与打包生成的css样式,是全局样式,如何做到样式隔离呢
  像vue样式的scoped,如何样式隔离只针对当前工作区呢
  css模块化配置如下(scss/less/stylus等相同)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module.exports = {
module:{
rules: [
{
test: /\.css$/,
loader: ['style-loader',{
loader: 'css-loader',
options: {
modules: true // true/local 开启模块化机制 默认global
}
}],
exclude: /node_modules/
}
]
}
};

  之前我们引入样式的方式如下

1
import "./app.css";

  重新打包后我们发现,app.css中标签样式可以显示,而类选择器、id选择器等样式没有生效
  我们查看一下打包后生成的样式会发现样式并没有丢失,但除标签名外,其他选择器名都帮我们改为了无意义的字符串
  也就是说,如果我们想用这些样式,就要知道我们写的类名对应生成的字符串是什么,那么如何获取这些名称呢
  modules为我们抛出了一个对象,key为我们定义的选择器名,value对应生成的字符串
  我们需要这样引入:

1
2
3
4
// 个人猜测 模块化是根据原有选择器名进行编码处理 再通过export default { 选择器名1: 编码选择器名1,选择器名2: 编码选择器名2,...  }
import CSS from"./app.css";

// 假如我们在app.css有一个样式 .avatar{ ... } 此时我们只需要获取CSS.avatar 并将其绑定到对应DOM上,即可生效

  默认hash编码,我们还可以指定编码方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
module.exports = {
module:{
rules: [
{
test: /\.css$/,
loader: ['style-loader',{
loader: 'css-loader',
options: {
modules: {
localIdentName: '[path][name]__[local]--[hash:base64:5]',
},
}
}],
exclude: /node_modules/
}
]
}
};
  • 字体打包

  当我们使用外部字体库时,很显然直接引用,我们需要对字体进行打包处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module.exports = {
module:{
rules: [
{
test: /\.(woff|woff2|svg|eot|ttf)/, // 常用字体库
loader: [{
loader: 'file-loader', // 还记得我们的图片打包么
options: { // 若不配置,默认打包只output文件下,并修改文件名为编码后的字符串
outputPath: "font/", // 输出的文件路径
name: '[name].[ext]' // 输出的文件名 ext是文件后缀名称
}
}],
exclude: /node_modules/
}
]
}
};

从0基础到大牛-webpack深入浅出超强完整版(二)

你可能只需要这一个教程

webpack小试牛刀 - ES6+ 转 ES5

  • 全局垫片 polyfill

  使用babel,需要下载 babel-loader @babel/core @babel/preset-env
  babel-loader 下载 npm install -D babel-loader @babel/core @babel/preset-env webpack
  配置如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module.exports = {
module: {
rules: [
{
test: /\.js$/, // 正则 匹配规则
exclude: /node_modules/, // 依赖文件已做过处理,无需再处理,节省打包时间
loader: "babel-loader", // 使用的loader
option: { // 配置 option的配置应参照使用的loader配置列表
presets: ["@babel/preset-env" ]
}
}
]
}
};

  打包生成的文件中,ES6语法将会转为ES5语法
  但并不是所有的ES6都可以被转化,ES6中一些内置API和功能(Promise set Map)、Object.assign等无法转化
  如果想要实现上述这些方法,我们需要使用垫片库(polyfill)npm install -D @babel/polyfill
  直接引入垫片库,会将所有方法都加载进来

1
2
3
4
5

import "@babel/polyfill";
const arr = [new Promise(()=>{}),new Promise(()=>{})];

// 此时 Object.assign Set Map等方法也会被打入,生成文件骤然变大

  如何实现按需加载呢?需要配置如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
module.exports = {
module: {
rules: [
{
rule: /\.js$/,
exclude: /node_modules/,
loader: "@babel-loader",
options: {
presets: [
["@babel/preset-env",{
"useBuiltIns" : "usage",
// usage: 按需加载 页面无需引入"@babel/polyfill"
// entry: 按需加载 页面需手动引入 import "@babel/polyfill"
// 自测时发现 entry比usage打出的包更轻量

"target": { // 目标 针对浏览器版本打包
ie: "9"
}
}]
]
}
}
]
}
};

  • babel的全局配置
      webpack中options的配置可以全部放置到babel全局配置文件中,其作用和用法完全一致。首先我们先将webpack中babel的options清除
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    module.exports = {
    module: {
    rules: [
    {
    rule: /\.js$/,
    exclude: /node_modules/,
    loader: "@babel-loader",
    }
    ]
    }
    };

  根目录下新建babel配置文件: .babelrc 并配置如下(此处注意JSON写法)

1
2
3
4
5
6
7
8
{
"presets" : [["@babel/preset-env",{
"useBuiltIns": "entry",
"targets": {
"chrome": 67
}
}]]
}

  • 局部垫片 transform-runtime

  polyfill是一个全局的垫片,会将方法全部扩展到原型上。如果是开发库或者框架,就需要使用局部垫片。
  下载transform-runtime npm install --save-dev @babel/plugin-transform-runtimenpm install --save @babel/runtime
  .babelrc配置如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"presets" : ["@babel/preset-env"],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"absoluteRuntime": false,
"corejs": false,
"helpers": true,
"regenerator": true,
"useESModules": false
}
]
]
}

  transform-runtime默认按需加载,无需配置
  corejs可取值false,2,3或{ version: 2 | 3, proposals: boolean },默认为false详见文档

  • 更改文件打包地址
1
2
3
4
5
6
7
const path = require("path"); // node
module.exports = {
output:{
filename: '[name].js',
path: path.resolve(__dirname,'dist'), // __dirname为当前文件路径 dist可更改为要打包的路径名 默认dist
}
}
  • 转换TS文件

  webpack支持Typescript打包,需先下载npm install --save-dev typescript ts-loader
  根目录下新建ts配置文件:tsconfig.json 并配置如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"compilerOptions": {
"module": "es6", // 模块的引入方式 es6|commonjs
"target": "es5", // 目标语法
"allowJs": true // 是否允许JS语法
},
"include": [ // 包含文件列表
"./src/",
"./"
],
"exclude": [ // 不包含的文件
"node_modules"
]
}

  并在webpack配置中新增loader

1
2
3
4
5
6
7
8
9
10
11
module.exports = {
module: {
rules: [
{
test: /\.ts$/,
loader: "ts-loader",
exclude: /node_modules/,
}
]
}
};

  TypeSearch类型约束查找地址

  • 图片打包

  下载file-loader npm install file-loader --save-dev
  webpack中新增图片转换的loader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module.exports = {
module : {
rules : [
{
test: /\.(png|jpe?g|gif)/,
use: {
loader: "file-loader",
options:{
name: "[name]-[hash:5].[ext]", // name:图片名 ext:图片后缀名 hash:5 取Hash前5位值 默认文件名为hash:5
outputPath: "images/" // 输出文件夹(output.path)下 打包输出地址
}
},
exclude: /node_modules/
}
]
}
}

  注:loader有多种引入写法
  另一个插件url-loader包含file-loader,比file-loader更强大。npm install -D url-loader
  将上述配置file-loader改为url-loader,将不再生成image文件,会自动将图片转为base64位并注入js中。现在我们修改配置,限制转Base64大小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
module.exports = {
module : {
rules : [
{
test: /\.(png|jpe?g|gif)/,
use: {
loader: "file-loader",
options:{
name: "[name]-[hash:5].[ext]",
outputPath: "images/",
limit: 1024 * 1 //1024 = 1kb 低于1kb生成base64位,以减少http请求
}
},
exclude: /node_modules/
}
]
}
}

从0基础到大牛-webpack深入浅出超强完整版(一)

你可能只需要这一个教程

webpack介绍

1.webpack发展历程

发布时间

  • webpack   v1.x   2014.02
  • webpack   v2.x   2017.01
  • webpack   v3.x   2017.06
  • webpack   v4.0   2018.05   稳定版4.32.2
  • webpack   v5.x   2019.xx   目前不是稳定的版本

模块化历程
 单页面应用(Vue React)都在用模块化

命名空间

 蛮荒时代的模块化

1
2
3
4
5
6
7
8
9
var obj = {};
obj.type = obj.type || {};
obj.type.method = function() {};
obj.type.add = function() {};
obj.type.sub = function(){};

obj.addin = obj.addin || {};
obj.addin.remove = function() {};
obj.addin.append = function() {};

 AMD与CMD的中原逐鹿

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// AMD 异步加载 依赖前置 先加载后使用
// 需要库文件 - require.js
// 定义AMD模块 文件名amd
define(function(a,b) {
'use strict';
return {
add: function(a,b) {
return a + b;
},
sub: function(a,b) {
return a - b;
}
}

});

// 加载AMD模块
require(['moduleA','moduleB','amd'],function(a,b,amd) {
console.log(a); // a模块
console.log(b); // b模块

amd.add(1,2); // 调用amd
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// CMD 同步加载 依赖后置 使用时引入 
// 需要库文件 - sea.js
// 定义CMD模块 文件名cmd
define(function(require,exports,module) {
var moduleA = require('moduleA'); // 需要时引入

exports = {
add: function(a,b) {
return a+b;
},
sub: function(a,b) {
return a - b;
}
}
});

// 加载CMD模块
seajs.use(['cmd'],function(cmd) {
cmd.add(1,2);
cmd.sub(2,3);
});

 commonJS - Node.js主要践行者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 一个文件就是一个模块 文件名common
var a = 10;
var b = 20;
function fun() {

};

// 导出方式 一
modules.export = {
a : a,
b : b,
fun : b
};

// 导出方式 二
// export.add = function(){}; ...


// 通过关键字require引入文件【相对路径/绝对路径引入】
var common = require('common');
console.log(common.a); // 10

 官方科普最硬核 - ES Module(ES6的模块化)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 使用方式 一
// 导出 文件名es1
export var a = 10;
export var add = function(a,b) {
return a + b;
}


// 引入
import { a,add } from 'es1';
add(3,5); // 调用

// 使用方式 二
// 导出 文件名es2
export default const b = {
return 20;
};

// 引入
import b from 'es2';
console.log(b);

*CSS模块化

  • OOCSS(面向对象)
  • AMCSS(属性模块)

webpack 4.32.2核心组成

  • entry : 命令行中写入口,配置文件中
  • output
  • module
  • plugins

 准备工作
  卸载旧的webpack版本 npm uninstall webpack -g
  下载最新的版本 npm install webpack -g
  检查当前webpack版本 webpack --version
  需要在本地文件内下载webpack,初始化package.json文件 npm init -y
  去下载 webpack 和 webpack-cli,3.x 的版本是不需要 webpack-cli,webpack 中集成了 webpack-cli, 4.X以后没有集成
  webpack4.x 以下的使用 webpack app.js 是可以的,4.x以后因为增加了一个多入口命令打包,需要指定出口文: webpack app.js -o bundle.js
  如果不加 –mode development 参数,默认是生产环境,会自动压缩代码
  当前可下载的webpack版本 npm info webpack webpack-5.x-bate(公测的版本)

 起手式
  根目录下新建配置文件,默认配置文件为 webpack.config.js
  你的第一个配置文件

1
2
3
4
5
6
7
8
9
module.exports = {
// 入口 相对文件地址
entry: './app.js',
// 出口
output: {
filename: 'bundle.js'
},
mode: "development"
};

  执行命令webpack后会生成 /dist/bundle.js 文件
  使用自定义名称的配置文件my-webpack-setting.js打包,请执行 webpack --config my-webpack-setting.js

  • 多入口打包

    1
    2
    3
    module.exports = {
    entry: ['app.js','app1.js','app2.js']
    }
  • 自定义出口文件名打包

    1
    2
    3
    4
    5
    6
    7
    8
    module.exports = {
    entry: {
    main: './app1.js',
    },
    output: {
    filename: '[name].js',
    }
    }

  将生成 /dist/main.js 文件

  • 多入口多出口文件打包
    1
    2
    3
    4
    5
    6
    7
    8
    9
    module.exports = {
    entry: {
    main: './app.js',
    select: './my_select.js'
    },
    output: {
    filename: '[name].js',
    }
    }

  将在dist文件夹下生成main.js与select.js
  注:多入口对应多出口,多入口对应单一出口配置时将会报错

  • ES6 module 打包
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    //  导出文件 es6.js
    export const add = function(a,b) {
    return a + b;
    }

    // 导入文件 app.js
    import { add } from './es6.js';
    console.log(add(4,7)); // 11


    // webpack配置文件
    module.exports = {
    entry: {
    app: './app.js',
    },
    output: {
    filename: '[name].js',
    },
    mode: "development"
    }

  结果生成 /dist/app.js 文件, 文件内包含 es6.js 与 app.js 代码

  • CommonJS 打包
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // 导出 common.js
    module.exports = {
    add : function(a,b) {
    return a + b;
    },
    sub: function(a,b){
    return a - b;
    }
    };

    // 导入 app.js
    const method = reqiore('./common,js');
    console.log(method.add(4,7)); // 11
    console.log(method.sub(8,3)); // 5

    // webpack配置文件 同上

  结果生成 /dist/app.js 文件, 文件内包含 common.js 与 app.js 代码

  • amd模块 打包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 导出 amd.js
define(function(require,factory) {
return {
mul: function(a,b) {
return a * b;
}
}
});

// 导入
require(['./amd.js'],function(method) {
console.log(method.mul(10,4)); // 40
});

// webpack配置文件 同上

  dist文件夹下将生成 app.js与 0.js
  0.js异步加载的内容(amd.js内容)

前端设计模式之依赖倒置原则(DIP)

前端原型的个人猜想

  相信每个学习javaScript的同僚,一定对一个说法非常熟悉『一切皆对象』.

  这个说法很好的将我们从现实社会连接进了程序世界,同时也解释了程序界很多古怪无法理解的东西.

  而事实真是这样么?在我写了这么久代码之后,我反而对javaScript最基本的东西产生了越发的迷茫和混乱.

  像隐式转换、原型链的继承这种更是完全无法想通,当我看到null和undefined时鬼知道我的脑子会爆炸出什么,最后扭合到一起,变成一堆的问号…

  现在我希望彻底能搞清祖师爷在创造javaScript是究竟是怎样的思路,这对我来说很难,但我知道当我搞清楚这个问题时,会有一个很大的飞跃,我不应该继续『不了解但不影响工作』在的舒适区混下去.

  感谢jQuery的源码,让我有了共享原型的概念,不然我一定会强制要求原型链必须是个有唯一顶点的树状结构。

  *请注意下面代码中,一定要将Object.prototype和Function.prototype当成是独立的一部分

1
Object.prototype.__proto__ == null   // true

  这里我们知道了Object.prototype的原型链向上指向是null,注意我没有说它继承自null,因为null是个很特别的类型,我们都知道原型的本质是个对象,所以它应该继承自对象.
  那这里怎么会是个null呢?我是猜想浏览器帮我们指向了null.
  太好了,至少有一个封闭的点了!我们继续.

1
Object.__proto__ == Function.prototype  // true

  大声尖叫,Object竟然继承自Function.prototype,『一切皆对象』并不是最后的真像!就好比我们好不容易发现了真菌细菌这些肉眼看不见的小东西,转眼我们又发现了分子原子,难道大世界无限的在嵌套我们不知道的小世界么?

  好在程序世界的构成元素就是这几个,根本不存在宇宙这种无休无止的构成一说,但程序世界与现实世界还有一个最大的区别,叫做『自举』,这里我们可以简单理解为『我可以创造我自己』.

  有了上面代码,就下面代码的成立.

1
Object instanceof Function == true

  但是我们还知道

1
Object instanceof Object == true

  这里是『我创造了我自己』么?不是的,这里完全是多绕了一步

1
2
Object.__proto__ == Function.prototype   // true
Function.prototype.__proto__ == Object.prototype // true

  目前还没有出现Object,我们又回到了Object.prototype,而Object.prototype.proto等于null这件事我们在第一步就已经证实过了.

  问题出在哪呢?当然还是Object.prototype的继承问题!刚刚说过了Object.prototype很有可能是Object的实例,并保留了其继承关系,而浏览器对其赋值为null.

  这完全出自我的个人想法,因为如果不是这样,这条原型链的解释则都不成立.

  再看这个

1
Function.__proto__ == Function.prototype  // true

  Function继承自自己

/* 这里非常明确了Object继承自Function.prototype */


/*我们知道了上面代码的关系 下面这句就理所当然了*/


/* 但我们还知道*/

webpack4.X 小白从0开始一步学到位

来自三刷老肖的学习视频,依个人理解有增删

webpack介绍

1.webpack是什么
  • 打包工具(模块)
  • 前端必不可少的工具
2. webpack的作用
  • 打包(把多个文件打包成一个js文件,减少服务器压力、带宽)
  • 转化(比如less/sass/ts)需要loader
  • 优化(SPA越来越盛行,前端项目复杂度高,webpack可以对项目进行优化)
3. webpack构成
  • 入口 (entry)
  • 出口 (output)
  • 转化器 (loader)
  • 插件 (plugins)
  • 开发服务器 (devServer)
  • 模式 (mode)
4. 安装webpack
1
2
3
4
5
npm install webpack-cli -g
// or
yarn add webpack-cli -g
// 验证webpack环境已经OK ?
webpack -v
5. 开发环境和生产环境
  • 开发环境(development): 就是你平时编写代码的环境
  • 生产环境(production): 项目开发完毕,部署上线
6. npm基本命令
  • 生成package.json:
    • npm init
    • npm init -y
  • 下载线上环境包依赖
    • npm install [包名,包名,包名…] –save
    • npm i [包名,包名,包名…] -S
  • 下载开发环境包依赖
    • npm install [包名,包名,包名…] –save–dev
    • npm i [包名,包名,包名…] -D

webpack4.x初探

1. 跑一跑webpack

  当前项目下,新建src文件,src文件下新建index.js
  index.js文件编写测试代码【console.log(‘Hello World’);】
  将src下index.js打包为dist下bundle.js,执行如下:

1
webpack src/index.js --output dist/bundle.js
2. webpack.config.js配置文件

  当前项目下执行npm init -y
  当前项目下新增webpack.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
const path = require('path');
module.exports = {
entry: {
app: './src/index.js'
},
output:{
filename: 'bundle.js',
path: path.resolve(__dirname,'/dist')
},
module: {},
plugins: [],
devServer:{},
};

3. 使用自定义webpack配置文件名 例:myself.config.js
1
webpack --config myself.config.js
4. package.json配置运行命令

  package.json文件scripts下配置命令
  配置webpack开发环境(dev)/线上环境(build)命令如下

1
2
3
4
"scripts": {
"dev": "webpack --mode development",
"build": "webpack --mode production",
},

  执行开发环境打包:npm run dev
  执行线上环境打包:npm run build
  mode:设置打包环境

多入口、多出口、html插件使用

1. 多入口打包
1
2
3
4
5
6
7
module.exports = {
entry: ['./src/index.js','./src/index2.js'],
output:{
filename: 'bundle.js',
path: path.resolve(__dirname,'dist')
},
};
2. 多出口
1
2
3
4
5
6
7
8
9
10
module.exports = {
entry: {
app: './src/index',
main: './src/index2.js'
},
output:{
filename: '[name].bundle.js',
path: path.resolve(__dirname,'dist')
}
};
3. html-webpack-plugin

  作用:生成页面
  依赖webpack webpack-cli
  安装插件:npm i html-webpack-plugin -D
  使用如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
...,
plugins:[
new HtmlWebpackPlugin({
template: './src/index.html',
title: '可配置标题',
hash: true,
minify:{
collapseWhitespace: true
},
})
]
};

1
2
3
4
5
<head>
<meta charset="UTF-8">
<title><%= htmlWebpackPlugin.options.title%></title>
</head>
<!--htmlWebpackPlugin 为插件定义名称,与js中自定义的插件名无关 -->

参数解析

  • title:HTML文档标题
  • filename: 文件名,默认为index.html,可设置
  • template: 版本地址,默认查询 src/index.ejs,若未查到,将自动生成模板
  • templateParameters: 允许重新模板参数
  • inject: 注入,允许对head、body等添加脚本
  • favicon: 设置网页顶部图标
  • meta: 在head中注入mate标签
  • base: 设置公共地址,如 https://example.com/path/page.html
  • minify: 是否压缩
  • hash: 是否生成哈希值,若为true将为所以注入文件加入哈希值
  • cache: 是否启用缓存,仅在文件有改动后才更新文件
  • showErrors: 是否报错
  • chunks: 是否仅允许添加指定块
  • chunksSortMode: 设置块加载排序方式
  • excludeChunks: 不允许加入的块
  • xhtml: 是否遵照xhtml加入link标签

HTML插件优化、服务器、热更新

1. 生成多页面
1
2
3
4
5
6
7
8
9
10
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
}),
new HtmlWebpackPlugin({
filename: 'index2.html',
template: './src/index2.html',
}),
...
]

  配置多个页面时,此处一定要配置filename
  mpa项目可用node读取文件,来生成多个模板
  默认js都会打入模板内

2. 指定文件打入指定模板

  将app文件打入index.html,将main文件打入index2.html,配置如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module.exports = {
entry: {
app: './src/index',
main: './src/index2.js'
},
plugins: [
new HtmlWebpackPlugin({
chunks:['app'],
template: './src/index.html',
}),
new HtmlWebpackPlugin({
chunks:['main'],
filename: 'index2.html',
template: './src/index2.html',
})
],
};

3. clean-webpack-plugin

  作用:清除文件/文件夹
  使用方式:

1
2
3
4
5
6
7
8
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
module.exports = {
...,
plugins: [
new CleanWebpackPlugin(),
],
devServer:{},
};

4. devServer

  下载: npm install webpack-dev-server
  package.json中配置启动项

1
2
3
4
5
"scripts": {
"dev": "webpack --mode development",
"build": "webpack --mode production",
"start:dev": "webpack-dev-server"
},

  配置一个基本的devServer

1
2
3
4
5
6
7
8
9
10
11
module.exports = {
...,
devServer:{
// 设置服务器访问的基本目录
contentBase: path.resolve(__dirname,'dist'),
// 服务IP地址,本地可设置 localhost / 127.0.0.1
host: 'localhost',
// 设置端口
port: '8090',
},
};

  项目中我们发现dist文件为空,因为devServer将contentBase下的文件放到了活动内存中
  devServer支持自动刷新

5. 热更新

  作用:不重新加载整个文档,保留之前的操作,仅对有变动的部分更改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const webpack = require('webpack');

module.exports = {
...,
devServer:{
// 设置服务器访问的基本目录
contentBase: path.resolve(__dirname,'dist'),
// 服务IP地址,本地可设置 localhost / 127.0.0.1
host: 'localhost',
// 设置端口
port: '8090',
// 是否开启热更新
},
plugins: [
new webpack.HotModuleReplacementPlugin()
]
};

6, 实现跨域

  利用devServer实现跨域
  将所有/api开头的请求,代理至本机3000端口,配置如下

1
2
3
4
5
6
7
8
devServer: {
proxy: {
'/api': {
target: 'http://localhost:3000',
pathRewrite: {'^/api' : ''}
}
}
}

  pathRewrite:重写

loader作用,处理css文件及压缩方式

1. loader介绍

  作用:加载器、转化器
  比如:less/scss转成css;ES7/8/9、JSX转成js
  loader从后向前加载

2. css loader

  我们编写了一个app.css文件,并在js文件中引用了它

1
import "./app.css";

  此时工具将会报编译错误,提示我们缺少loader,下面我是使用loader将其转化
  下载css相关的两个最核心loader:style-loader与css-loader
  loader基础配置如下:

1
2
3
4
5
6
7
8
9
10
11
module.exports = {
module: {
rules: [
{
test: /\.css$/,
loader:['style-loader','css-loader'],
exclude: /node_modules/
}
]
},
}

关于loader的写法

  • loader: [‘loader1’,’loader2’,…]
  • use: [‘loader1’,’loader2’,…]
  • use: [{loader: ‘loader1’},{ loader: ‘loader2’ },…]

css-loader先于style-loader解析
css-loader用来解析文件,style-loader将注入到模板内(在模板的head标签内注入style标签,并将样式放入style标签内)

3. 文件压缩

  4.x版本之前,压缩插件使用uglifyjs-webpack-plugin
  4.x版本之后,仅需将模式改为线上模式,将自动生成压缩文件

处理图片,分离css

1. 图片处理

  当我们在css中引用一张路径正确的图片时,编译工具会报错(当时直接引入css的时候也是这样)
  图片转化我们经常会用到两个loader: url-loader和file-loader
  url-loader: 支持js引入文件,并将文件输出到output的文件夹
  file-loader与url-loader相似,但比url-loader功能还要强大
  url-loader使用如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module: {
rules: [
{
test: /\.(png|jpg|gif|jpeg)$/i,
use: {
loader: 'url-loader',
options: {
limit: 50*1000,
outputPath: 'image',
}
},
exclude: /node_modules/
}
]
},

  背景图小于50kb时,图片会生成base64位

2. 图片分离

  图片分离插件 extract-text-webpack-plugin 4.0版后已废弃
  4.0版后请使用mini-css-extract-plugin
  使用如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
module: {
rules: [
{
test: /\.css$/,
loader:[{
loader: MiniCssExtractPlugin.loader,
options:{
publicPath: ''
}
},'css-loader'],
exclude: /node_modules/
},{
test: /\.(png|jpg|gif|jpeg)$/i,
use: {
loader: 'url-loader',
options: {
outputPath: 'image',
limit: 50*1000
}
},
exclude: /node_modules/
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[id].css'
}),

],
}

less/sass处理,添加前缀,消除冗余css

1. 处理less

  下载转化less的loader: npm install less less-loader -D
  配置如下:

1
2
3
4
5
6
7
8
9
module: {
rules: [
{
test: /\.less$/,
use: ['style-loader','css-loader','less-loader'],
exclude: /node_modules/,
}
]
},

2. 处理sass

  下载转化sass的loader: npm install node-sass sass-loader -D
  配置如下:

1
2
3
4
5
6
7
8
9
module: {
rules: [
{
test: /\.s(a|c)ss/,
use: ['style-loader','css-loader','sass-loader'],
exclude: /node_modules/
}
]
},

3. 自动添加css前缀

  CSS预处理器: postCss
  下载预处理器loader:npm install postcss-loader autoprefixer -D
  项目根目录下新增postcss配置文件: postcss.config.js,配置如下

1
2
3
4
5
module.exports = {
plugins: [
require('autoprefixer')
]
}

  webpack配置如下

1
2
3
4
5
6
7
8
9
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader','css-loader','postcss-loader'],
exclude: /node_modules/
}
]
},

  目前遇到项目中不显示前缀,但demo中就可以,还在排查原因

4. 消除冗余CSS代码

  下载依赖:npm install purifycss-webpack purify-css -D
  paths参数用来设置需要分析的路径地址
  配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
   
const path = require('path');
const glob = require('glob');
const PurifyCssPlugin = require('purifycss-webpack');

module.exports = {
plugins: [
new PurifyCssPlugin({
paths: glob.sync(path.join(__dirname, 'dist/*.html'))
})
]
}

SourceMap、babel、react环境配置

1. SourceMap

  webpack 4.x版本默认在development环境下自动开启调试模式
  webpack 3.采用sourceMap

1
2
3
4
module: {
...,
devTool: 'source-map'
},
2. babel

作用:

  • babel用来编译js
  • 能够轻松使用ESnext转化
  • 支持jsx等语法

  babel核心模块: babel-loader @babel/core @babel/preset-env
  配置如下:

1
2
3
4
5
6
7
8
9
10
11
module: {
rules: [
test: /\.jsx?$/,
exclude: /node_modules/,
use: [{
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}]
]
}
3. 支持JSX语法

  下载插件:npm install react react-dom @babel/preset-react -D
  项目根目录下新建babel配置文件.babelrc.json

1
2
3
4
5
6
7
8
{
"presets": ["@babel/preset-react"],
"env": {
"development": {
"presets": [["@babel/preset-react", { "development": true }]]
}
}
};

JSON配置、模块化设置、静态资源、插件

1. 使用模块化

  以提取rules为例
  新建rules模块文件webpack.rule.js
  复制模块规则并通过module.exports导出
  webpack配置文件引入如下

1
2
3
4
5
6
const ruleConfig = require('./webpack.rule');
module.exports = {
module: {
rules: ruleConfig
},
}

  在webpack中使用JSON

  • webpack 3.X之前使用json-loader
  • webpack 3.X之后默认可自动识别
2. 静态资源输出

  使用插件copy-webpack-plugin
  配置如下

1
2
3
4
5
6
7
8
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = {
plugins: [
new CopyWebpackPlugin([
{from : path.resolve(__dirname,'src/assets'),to: './assets'},
])
]
}

  注:默认打入dist文件

优雅使用第三方库、提取JS文件、优化

1. 使用第三方库

   ① 通过npm引入
   ② 通过webpack内置插件ProvidePlugin,向全局暴露,使用方法如下

1
2
3
4
5
6
7
8
const webpack =  require('webpack');
module.exports = {
plugins: [
new webpack.ProvidePlugin({
$: 'jquery',
})
]
}

  通过ProvidePlugin引入与通过import引入的区别:

  • 通过import引入后,无论是否使用,都会被打包,这样会产生大量冗余js
  • ProvidePlugin只有在使用后才会打包
2. 提取JS文件

  在webpack 3.x及之前使用CommonsChunkPlugin
  webpack 4.x使用SplitChunksPlugin,使用如下

1
2
3
4
5
6
7
8
const webpack = require('webpack');
module.exports = {
optimization: {
splitChunks: {

}
}
}

  最后,这套课程老肖录制于18年初,截至到现在已经有一些插件、loader或写法发生了变化,我已对其做了少量调整,即便如此,对于新手而言这一定是一个很棒的课。
  这套课使自己快速入了webpack的门,每看一遍都有新收获,后面我将再写一篇由浅入深更全面、更深入的教程课,但老肖这套,唯有纸笔记之算自己才觉得可以算完结,好在我做到了。

webpack优化

开发阶段

1 开启多核压缩JS
  uglifyjs-webpack-plugin

2 兼容面板-webpack速度显示
  speed-measure-webpack-plugin

3 开启通知面板
  webpack-build-notifier

4 开启打包进度
  progress-bar-webpack-plugin

5 开发面板更清晰
  webpack-dashboard

6 开启窗口的标题
  node-bash-title

上线阶段

1.es6不需要编译

2.前端缓存负载
  webpack-manifest-plugin

3.真正的loading

1
2
3
4
5
new HtmlWebpackPlugin({
loading:{
html: '加载中......'
}
})

```html
 <%= htmlWebpackPlugin.options.loading.html %> 
``` 

4.分析打包结果
  bundlesize
  webpack-chart

5.文件压缩
  uglifyjs-webpack-plugin
  webpack-parallel-uglify-plugin

6.happypack

7.devtool

8.cache-loader