2019-12-6-前端webpack打包教程

概述

什么是webpack

其实就是模块打包机:

它做的事情是,分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript等),并将其转换和打包为合适的格式供浏览器使用。

为什么要使用webpack

模块化开发

程序员在开发时可以分模块创建不同的js、css等小文件方便开发,最后使用webpack将这些小文件打包成一个文件,减少了http的请求次数。

编译typescript. ES6等高级js语法

随着前端技术的强大,开发中可以使用javascript的很多高级版本,比如: typescript. ES6等 ,方便开发webpack可以将打包文件转换成浏览器可识别的js语法。

CSS预编译

webpack允许在开发中使用SassLess等原生CSS的扩展技术,通过sass-loader.lessloader将Sass 和Less的语法编译成浏览器可识别的css语法。

=== 博主觉得模块化开发和编译高级js语法这确实很赞,原因你往下看~

模块化的缺点

emmm…就是用了模块化之后文件会比较多,难找emmm…(这算缺点么)

Node 、ES6和webpack的前世今生

模块(module)

什么是模块化呢?

简单点说就是将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。

在Node和ES中均可以实现模块化的功能

ES6模块化

先说ES6的,在ES6中有个新特性是模块化,之前是没有的;现在开始教大家怎么使用吧

模块功能主要由两个命令构成:exportimportexport命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。

一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。

export导出命令

导出一个变量

1
2
3
4
5
6
7
// test01.js  
// 第一种写法
export var test01 = 'value01';
export var test02 = 'value02';
// 第二种写法 , 这种写法比较清晰,一下就能知道导出了哪些变量。
// 其实是跟第一种写法是等价的
export { test01, test02 };

导出一个函数

1
2
3
export function sayHello() {
return "helloworld";
};

重命名变量

通常情况下,export输出的变量就是本来的名字,但是可以使用as关键字重命名。

注意事项

需要特别注意的是,export命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系。

1
2
3
4
5
6
// 报错
export 1;

// 报错
var m = 1;
export m;

上面两种写法都会报错,因为没有提供对外的接口。第一种写法直接输出 1,第二种写法通过变量m,还是直接输出 1。1只是一个值,不是接口。正确的写法是下面这样。

1
2
3
4
5
6
7
8
9
10
// 写法一
export var m = 1;

// 写法二
var m = 1;
export {m};

// 写法三
var n = 1;
export {n as m};

上面三种写法都是正确的,规定了对外的接口m。其他脚本可以通过这个接口,取到值1。它们的实质是,在接口名与模块内部变量之间,建立了一一对应的关系。

同样的,functionclass的输出,也必须遵守这样的写法。

export命令可以出现在模块的任何位置,只要处于模块顶层就可以。如果处于块级作用域内,就会报错,下一节的import命令也是如此。这是因为处于条件代码块之中,就没法做静态优化了,违背了 ES6 模块的设计初衷。

1
2
3
4
function foo() {
export default 'bar' // SyntaxError
}
foo()

上面代码中,export语句放在函数之中,结果报错。

import导入命令

使用export命令定义了模块的对外接口以后,其他 JS 文件就可以通过import命令加载这个模块。

1
2
3
// 指定要从其他模块导入的变量名。
// 大括号里面的变量名,必须与被导入模块(test01.js)对外接口的名称相同。
import {test01 , test02} from './test01.js'

学过解构赋值的朋友是不是感觉似曾相识?

重命名

如果想为输入的变量重新取一个名字,import命令要使用as关键字,将输入的变量重命名。

1
import {test01 as hello, test02 as world} from './test01.js'

注意事项

import命令输入的变量都是只读的,因为它的本质是输入接口。也就是说,不允许在加载模块的脚本里面,改写接口。

1
2
3
4
5
6
7
import {a} from './yyy.js'
// 脚本加载了变量a,对其重新赋值就会报错,因为a是一个只读的接口。
a = {}; // Syntax Error : 'a' is read-only;
// 但是,如果a是一个对象,改写a的属性是允许的。
a.foo = 'hello'; // 合法操作

// 这特性怎么感觉和Java里面的常量有点相似呢?

import后面的from指定模块文件的位置,可以是相对路径,也可以是绝对路径,.js后缀可以省略。如果只是模块名,不带有路径,那么必须有配置文件,告诉 JavaScript 引擎该模块的位置。

1
import {myMethod} from 'util';

上面代码中,util是模块文件名,由于不带有路径,必须通过配置,告诉引擎怎么取到这个模块。

注意,import命令具有提升效果,会提升到整个模块的头部,首先执行

1
2
3
foo();

import { foo } from 'my_module';

上面的代码不会报错,因为import的执行早于foo的调用。这种行为的本质是,import命令是编译阶段执行的,在代码运行之前。

由于import是静态执行,所以不能使用表达式和变量,这些只有在运行时才能得到结果的语法结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 报错
import { 'f' + 'oo' } from 'my_module';

// 报错
let module = 'my_module';
import { foo } from module;

// 报错
if (x === 1) {
import { foo } from 'module1';
} else {
import { foo } from 'module2';
}

上面三种写法都会报错,因为它们用到了表达式、变量和if结构。在静态分析阶段,这些语法都是没法得到值的。

最后,import语句会执行所加载的模块,因此可以有下面的写法。

1
import 'lodash';

上面代码仅仅执行lodash模块,但是不输入任何值。

如果多次重复执行同一句import语句,那么只会执行一次,而不会执行多次。

1
2
import 'lodash';
import 'lodash';

上面代码加载了两次lodash,但是只会执行一次。

1
2
3
4
5
import { foo } from 'my_module';
import { bar } from 'my_module';

// 等同于
import { foo, bar } from 'my_module';

上面代码中,虽然foobar在两个语句中加载,但是它们对应的是同一个my_module实例。也就是说,import语句是 Singleton 模式。

export default 命令

上面我们说了导入导出,发现导入定义的变量名要和导出的变量名是一致,这样肯定不友好,难道我们每次导入的时候都要看看源模块文件嘛

1
2
3
4
5
6
7
8
// 导出一个匿名函数
export default sayHello(){
return 'helloworld';
}
// 导出一个非匿名函数
export default function sayHello(){
return 'helloworld';
}

下面比较一下默认输出和正常输出。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 第一组
export default function crc32() { // 输出
// ...
}

import crc32 from 'crc32'; // 输入

// 第二组
export function crc32() { // 输出
// ...
};

import {crc32} from 'crc32'; // 输入

上面代码的两组写法,第一组是使用export default时,对应的import语句不需要使用大括号;第二组是不使用export default时,对应的import语句需要使用大括号

export default命令用于指定模块的默认输出。显然,一个模块只能有一个默认输出,因此export default命令只能使用一次。所以,import命令后面才不用加大括号,因为只可能唯一对应export default命令。

本质上,export default就是输出一个叫做default的变量或方法,然后系统允许你为它取任意名字。所以,下面的写法是有效的。

正是因为export default命令其实只是输出一个叫做default的变量,所以它后面不能跟变量声明语句。

1
2
3
4
5
6
7
8
9
10
11
12
// modules.js
function add(x, y) {
return x * y;
}
export {add as default};
// 等同于
// export default add;

// app.js
import { default as foo } from 'modules';
// 等同于
// import foo from 'modules';

正是因为export default命令其实只是输出一个叫做default的变量,所以它后面不能跟变量声明语句。

1
2
3
4
5
6
7
8
9
// 正确
export var a = 1;

// 正确
var a = 1;
export default a;

// 错误
export default var a = 1;

上面代码中,export default a的含义是将变量a的值赋给变量default。所以,最后一种写法会报错。

同样地,因为export default命令的本质是将后面的值,赋给default变量,所以可以直接将一个值写在export default之后。

1
2
3
4
5
// 正确
export default 42;

// 报错
export 42;

上面代码中,后一句报错是因为没有指定对外的接口,而前一句指定对外接口为default

进行模块化测试

新建一个test01.js

1
export default 'helloWrold';

新建test02.js

1
2
import helloWrold from './test01.js'
console.log(helloWrold)

然后将test02.js导入到index.html文件中,在浏览器中打开发现报错

1
Uncaught SyntaxError: Cannot use import statement outside a module

ヾ(≧O≦)〃嗷~ 发现模块化不能被浏览器识别,只能通过其它的工具将es6的语法变成es5,

例如:babelbabel网址

NodeJS模块化

nodeJs采用CommonJS来进行模块化,环境必须是在node里面

导出

1
2
3
4
5
6
7
var hello = 'world';
// 导出一个变量
modules.exports.hello = hello;
// 等价于
exports.hello = hello;

// exports = hello; 报错

modules.exports和exports 两者的区别
看起来 exports 变量和 module.exports 变量似乎是同一个变量

但实际上还是有些区别,比如 exports 不能直接赋值

这是因为 exports 只是对 module.exports 的一个全局引用,会在程序加载时初始化为一个空对象

而这个对象 exports 不能被赋值或者重写,那样 exports 就不再指向 module.exports

1
2
3
4
5
var hello = 'helloworld';
// 这样再导入的时候会报找不到hello这个变量
exports = hello;
// 正确
module.exports = hello;

导入

使用require关键字导入

定义一个 test01.js

1
2
let hello = 'helloworld'
module.exports.hello = hello;

定义一个test02.js

1
2
let hello = require('./test01.js')
console.log(hello)

运行(需要有nodeJs的环境)

cmd> node test02.js

result: { hello: ‘helloworld’ }

因为我们往module.exports中导出变量相当于添加属性,导入的自然也就是一个对象

深入解析 require函数

1
2
3
4
5
// 这是一个相对路径
require('./test01.js')
// 可省略 后缀.js
require('./test01') // 正确
require('test01') // 错误

这是因为在没有具体路径的时候,require 会首先检查 node.js 核心模块(如 “fs”),

然后检查 当前路径下的node_modules目录,最后检查环境变量 NODE_PATH 指定的目录

Webpack和Node、ES6的不解之缘

说完了Node、ES6的前世今生之模块化编程,我们再来谈谈webpack

在上面的演示中可以发现,我们对于node和Es6的模块化都不能直接运行,前者需要借助于node.js的环境,

后者则需要进行语法的降低,变成浏览器可识别的ES5的语法。这时候,webpack兄弟来了,他的出现无疑于让我们看到光明……

不管你是node的模块化还是es6的模块化都可以运行,使用webpack打完包之后都可以在浏览器运行

前提条件 :webpack运行需要借助于node环境,必须先安装node.js

1.安装webpack npm install webpack -g 全局安装 –save-dev 是当前目录安装 ,默认下载的是最新版,目前应该是4

2.安装webpack-clinpm install webpack-cli -g

Node、ES6和webpack的一次完美结合

编写test01.js

1
export default 'helloWorld'

编写test02.js

1
2
import h from  './estest01.js'
console.log(h)

编写index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="./bundle.js"></script>
</head>
<body>

</body>
</html>

进入安装npm的安装目录

  • 如果你是全局安装,默认是在C:\Users\你的用户名\AppData\Roaming\npm下,

  • 如果是--save-dev安装,默认在当前目录的node_modules目录下

手动输入命令运行打包

进入到npm的安装目录里输入命令:

webpack C:\Users\你文件的路径\test02.js -o ./bundle.js输出到bundle.js中,然后把bundle.js复制到项目目录,引入到index.html文件中即可

浏览器打开运行:console : helloWorld

同理,用nodejs编写的模块化文件也是如此运行,这里不多演示

注意,我们进行打包的时候要使用文件是导入improt或者require的文件

使用配置打包

接着使用 npm init命令初始化项目,会让你输入很多东西,直接回车就完事,不用管那么多。

然后在当前目录下会生成一个package.json的文件,在scripts对象里面添加一个属性

1
"start": "webpack"

package.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"name": "nodejs",
"version": "1.0.0",
"description": "",
"main": "bundle.js",
"dependencies": {},
"devDependencies": {
"html-webpack-plugin": "^3.2.0",
"webpack": "^4.41.2",
"webpack-cli": "^3.3.10",
"webpack-dev-server": "^3.9.0"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start":"webpack"
},
"author": "",
"license": "ISC"
}

新建一个webpack.config.js文件 ,这是webpack的配置文件

1
2
3
4
5
6
7
8
9
10
// webpack4的配置
module.exports = {
// webpack4需要添加这个配置,development为开发环境,production为生产环境
mode: "development",
entry: "./test02.js", // 唯一入口文件
output: {
path: __dirname + "", // 打包后的文件存放的地方
filename: "bundle.js" // 打包后输出文件的文件名
}
}

命令行输入 : npm start即可看到bundle.js生成,不过如此嘛

webpack热部署

webpack-dev-server开发服务器,它的功能可以实现热加载并且自动刷新浏览器。

使用webpack-dev-server需要安装webpack、webpack-dev-server和 html-webpack-plugin三个包。

npm install webpack --save-dev

npm install webpack-dev-server --save-dev

npm install html-webpack-plugin --save-dev

接着使用 npm init(如果你在上一节中运行了就不需要再运行一遍)命令初始化项目,

会让你输入很多东西,直接回车就完事,不用管那么多。

然后在当前目录下会生成一个package.json的文件,在scripts对象里面添加一个属性

1
"dev": "webpack-dev-server --inline --hot --open --port 8888"

-inline:自动刷新

--hot :热加载

-port:指定端口

-open :自动在默认浏览器打开

--host :可以指定服务器的ip ,不指定则为127.0.0.1 ,如果对外发布则填写公网ip地址

此时我的package.json长这个样子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"name": "nodejs",
"version": "1.0.0",
"description": "",
"main": "bundle.js",
"dependencies": {},
"devDependencies": {},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "webpack-dev-server --inline --hot --open --port 8888"
},
"author": "",
"license": "ISC"
}

配置webpack.config.js

在目录下创建webpack.config.js,webpack.config.js是webpack的配置文件。 在此文件中可以配置应用的入口文件、输出配置、插件等,其中要实现热加载自动刷新功能需要配置html-webpack-plugin插件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var htmlwp = require('html-webpack-plugin');
module.exports = {
entry: './estest02.js', //指定打包的入口文件
output: {
path: __dirname, // 注意_dirname表示webpack.config.js所在目录的绝对路径
filename: 'build.js' //输出文件
},
plugins: [
new htmlwp({
title: '首页', //生成的页面标题<head><title>首页</title></head>
filename: 'index.html', //webpack-dev-server在内存中生成的文件名称 ,自动将build注入到这个页面底部,才能实现自动刷新功能
template: 'index.html' //根据index1.html这个模板来生成(这个文件请程序员自己生成)
})

]
}

然后npm run dev运行即可,会自动打开浏览器,端口是8888

-------------本文结束 感谢您的阅读-------------
湖南有北 wechat
扫码进群哦~~
你可以对我打赏哦