2

写在前面

本文关键字:exports module.exports import export 模块化 node变量 require webpack配置项
如果你对以上关键字有一些疑问,可能这篇文章会对你有一点点帮助,文章主要为你比较深入的解释了node和es6中的模块化,最后列出了一个基本的webpack配置,并对每项做出了说明

如果你有疑问可以加qq群和小伙伴一起讨论,群号:613202492

虽然本人比较懒,但是这次会迅速出下一篇博客的,名字暂定为:跟着vue官方学webpack配置

webpack基本概念

demo1 webpack 是什么

webpack是一个模块打包工具;JavaScript中一个单一功能的方法(函数),我们就可以把它叫做一个模块;demo1演示如何把代码模块化,并用webpack是如何来打包这些模块的。

演示三种不同的代码

传统代码
a.js
function getOrder() {
    console.log('我实现的是下单功能');
}
b.js
function pay() {
    getOrder();
    console.log('我实现的是付款功能');
}
c.js
pay()
index.html
<script src="a.js"></script>        
<script src="b.js"></script>        
<script src="c.js"></script>        
模块化后的代码(node.js写法,所以只能在node中运行,所以要用webpack打包后才能在浏览器中运行)
a.js
function getOrder() {
    console.log('我实现的是下单功能');
}
exports.getOrder = getOrder;
b.js
const getOrder = require('./a.js').getOrder;
function pay() {
    getOrder();
    console.log('我实现的是付款功能');
}
exports.pay = pay;
c.js
const pay = require('./b.js').pay;
pay();
index.html    
<script src="./c.js"></script>        
打包后的代码
d.js
/*这里会生成其它一些webpack辅助的代码*/
function getOrder() {
    console.log('我实现的是下单功能');
}
function pay() {
    getOrder();
    console.log('我实现的是付款功能');
}
pay()
index.html
<script src="d.js"></script>        

使用webpack打包

很明显,模块化的代码就是我们平时希望书写的代码,但是打包后的代码是我们希望生成的代码,所以我们要使用webpack把模块化的代码打包。

安装node.js

因为需要通过npm安装webpack,所以要先下载node.js并安装。 node官方下载地址 点左边那个版本下载就好了。

安装webpack

安装好了node.js就可以使用npm了,它是一个包管理器,最重要的功能就是帮你下载(管理)各种包,所谓包就是具有完整功能的一组或者一个js文件,通常一个包是多个模块构成的,你可以简单理解为小功能叫模块,多个模块构成一个大的功能叫包。

在命令行中输入: npm install webpack -g 就可以全局安装webpack了,如果网比较慢,可以使用淘宝的cnpm镜像(npm 下载源都是国外的,网络慢点,cnpm使用的是国内的镜像,所以快点)。

在命令行中输入: npm install -g cnpm --registry=https://registry.npm.taobao.org 就可以使用淘宝的镜像了,安装webpack就可以使用 cnpm install webpack -g

使用webpack打包

cd demo1
webpack c.js d.js

webpack打包的好处

生成的d.js 就是我们需要生成的代码,简单的说其实就是把几个文件合并成一个文件了,这样做有一个很大的好处:

减少http请求,减少脚本的阻塞

当html文件下载完成后,浏览器读取了html文件,会自动去逐个发起请求去下载html中一切可以下载的文件,比如图片、css、js文件,多一个script就多一次http请求,所以合并script可以减少http请求,这样就能减少网页等待的时间(页面空白时间);

而script不仅仅会影响到自己,还会影响到其它文件的下载,因为script会阻塞其它资源的下载(意思就是在下载script的时候不能下载其它文件),在低版本浏览器中连其它的script也会阻塞。

写模块化代码的好处

通过上面这个简单的例子,你可以发现webpack其实只是一个辅助性的工具,主要目的还是为了最方便的写模块化的代码

解决文件依赖问题

上面的例子中c.js中执行了a.js的代码,c.js高度依赖了a.js,所以在引入c.js前必须引入a.js,js文件过多后,容易引起混乱,而模块化后只需要在当前文件引入就可以。(容易忘记依赖和顺序)

避免全局变量污染、函数名冲突

使用模块化后,你可以明显的发现已经不存在全局变量了,因为你完全可以用一个新的名字来命名引入的的文件。例如你可以这样引入jquery:const $$$ = require('jquery')

其它好处

模块化本质上来说解决的问题并不多,主要就是依赖和冲突问题,但是衍生的好处非常多,比如合并两个项目更容易了,分工合作更简单了,一个函数两百行的代码越来越少了,要知道这些问题在很多场景下在过去都是挺头疼的一件事

demo2 前端模块化

node.js 模块化

node.js 是啥
  1. node.js 是一个软件,和浏览器、酷狗、爱奇艺、微信一样,是一个没有界面的软件。
  2. node.js 安装好后,就可以用node来运行js文件了,既然node是一个软件,当然可以像浏览器一样运行js文件(^__^)
  3. 用node中运行的js文件,可以使用node提供的模块和全局变量,也可以使用node提供的require函数来加载模块文件
node全局变量

node.js中有个全局变量global,它就像浏览器中的window对象一样,都是运行环境提供的全局变量,在浏览器的运行环境中提供的是window对象,在node.js中提供的global对象

a.js
console.log('global的类型:' + typeof global);
console.log('开始打印global的属性和值');
for(var key in global) {
    console.log('key值:' + key);
}
node a.js 打印输出的结果:

global的类型:object
开始打印global的属性和值
key值:DTRACE_NET_SERVER_CONNECTION
key值:DTRACE_NET_STREAM_END
key值:DTRACE_HTTP_SERVER_REQUEST
key值:DTRACE_HTTP_SERVER_RESPONSE
key值:DTRACE_HTTP_CLIENT_REQUEST
key值:DTRACE_HTTP_CLIENT_RESPONSE
key值:global
key值:process
key值:Buffer
key值:clearImmediate
key值:clearInterval
key值:clearTimeout
key值:setImmediate
key值:setInterval
key值:setTimeout
key值:console

和浏览器运行环境上window上的变量可以直接使用一样,global对象上的属性也可以直接使用,比如平时我们用的console.log('xxx')之所以能用,其实是调用的global.console.log('xxx')

当然你也可以自己跟global上加上变量,这样你就可以在全局使用它了,比如

a.js
//在全局对象上加上了属性aaa,就可以直接使用aaa这个变量了
global.aaa = 'xxxxx'
console.log(aaa)

//这里要正确的打印出aaa,需要a.js在b.js之前执行
b.js
console.log(aaa)

和浏览器中的window对象不同的是,在浏览器中所有var申明的对象,会挂载到window上,但是在node中并不会,比如

a.js
var a = 'xxx';
console.log('window.a的值是:' + window.a);

把上面这段代码拷贝到index.html文件中执行,会成功执行打印出 window.a的值是:xxx

a.js
var a = 'xxx';
console.log('global.a的值是:' + global.a);

上面这段js在node a.js 执行的结果是:global.a的值是:undefined,从而证实了var声明的变量并不会挂载到global全局变量上

在打印global的key值的时候,你可能会注意到里面有个global,你可以console.log(global.global),会发现global和global的值是一模一样的,也就是node内部做过这样的操作:global = global.global,这是为了能直接使用global这个变量名

模块级变量

有心的同学可能会发现require函数和exports等变量都不在全局变量中,那我们为什么能直接使用它们的呢,其实node中还有几个模块级别的变量,每个js文件都有独立的作用域,这也是你在a.js中申明一个var a然后在b.js中并不能引用的原因

你可以把整个js文件看做是一个自执行函数,这也是在曾经原生js模块化的写法,只是node为它增加了几个参数

(function (exports, require, module, __filename, __dirname) { 
  // 你写的a.js的代码在这里
}); 
(function (exports, require, module, __filename, __dirname) { 
  //  你写的b.js的代码在这里
}); 
(function (exports, require, module, __filename, __dirname) { 
  //  你写的b.js的代码在这里
}); 

看到了吗,其实每一个js文件都被node封装成了一个函数,并为这个函数加上了exports、equire、module、 __filename、 __dirname这几个参数,这也是为什么你可以直接在js文件中使用这几个变量的原因.

我们打印一下这5个模块级变量:

b.js
console.log('__filename:' + __filename);
console.log('__dirname:' + __dirname);
console.log('require:' + require);
console.log('========================');

console.log('module的类型:' + typeof module);
console.log('module.exports的类型:' + typeof module.exports);
console.log('========================');

console.log('开始打印module的属性和值');
for(var key in module) {
    console.log(key + '===>' + module[key]);
}
console.log('========================');

console.log('开始打印module.exports的属性和值');
for(var key in module.exports) {
    console.log(key + '===>' + module.exports[key]);
}
console.log('========================');

通过打印可以看出 __dirname是文件路径,__filename是文件路径加文件名,还有三个变量是和模块化相关的,放在下面两节单独讲(^__^)

使用require引用模块

通过require可以引用模块或者js文件两类

1.1 第一种引入文件,和demo1中演示的一样,直接require('./a.js'),其中.js这种后缀可以省略,引入一个js文件一定要带路径!!!可以是../ 、 ./ 等相对路径 也可以是 / 这种绝对路径

1.2.1 第二种是引入模块,只要require中的参数不带路径而是直接名字,例如requie('express'),这就是引用一个模块。node优先引入node.js自带的核心模块,node有接近20个核心模块,可以通过官网查询,官网左侧的这些模块都是核心模块,都是可以直接通过require('模块名字'),然后就可以使用的。下面演示一下http模块(node其中一个核心模块)的用法:

a.js
var http = require('http')
var callBack = function(req, res) {
    res.write('<div>hello world</div>')
    res.end()
}
http.createServer(callBack).listen(8000)

cd 到a.js所在目录下 >> 命令行输入:node a.js >> 在浏览器输入:localhost:8000 就能访问到服务器输出的div了

1.2.2 demo1中我们讲到可以通过npm安装一个包(其实就是安装一个库),然后就可以像使用核心模块一样使用它了,如果require('模块名')找不到核心模块,就会找当前路径下的node_modules文件夹中的模块(npm安装的模块都会安装到node_modules文件夹中),

使用第三方的包和核心模块是一样的,都是通过require,它们具体的用法就得看这个包(库)的api了。

使用node.js输出模块

前面说过可以通过require引入一个js文件(模块),当然我们需要输出一个模块,这样我们才能知道通过require引入过来的到底是什么,在node中是通过module和exports两个对象来实现的

通过require引入的值就是被引入文件中mudule.exports的值

1. 使用 module.exports 输出

c.js
var a = function() {
    console.log('a');
};

var b = function() {
    console.log('b');
};

var c = 'abcefg';

//第一种用法,在module.exports对象上添加属性
module.exports.a = a;
module.exports.b = b;
module.exports.c = c;

//第二种用法,直接给module.exports重新赋值
module.exports = {'a':a,'b':b,'c':c};
    
d.js
var util = require('./c.js');

util.a();
util.b();
console.log(util.c);

虽然演示的是两种用法,其实都是修改module.exports的值,因为在d.js中require('./c.js')的值就是c.js中module.exports的值。(注意这里不能是require('c.js'),因为不带路径的时候,require会把它当做核心模块或者第三方模块来寻找,而不是js)

2. 使用exports输出

之所以会设计出exports这个对象,主要是为了省略module.exports前面的module,你可以理解成在node.js内部是这样的,exports = module.exports

所以你对exports的属性赋值,和对module.exports的属性赋值是一个效果,因为最后都可以修改module.exports这个对象

但是直接给exports对象赋值是达不到效果的,因为这样并不能修改module.exports的值

c.js
var a = function() {
    console.log('a');
};

var b = function() {
    console.log('b');
};

var c = 'abcefg';

//exports只有一种用法
exports.a = a;
exports.b = b;
exports.c = c;

//下面用法是错误的,因为直接给赋值并不能改变module.exports的值
exports = {'a':a,'b':b,'c':c};
es6 模块化

除了node.js模块化,还有es6也提供了模块化的功能,es6提供了import 和 export来实现模块化(注意和node.js提供的exports单词并不一样)

var App = require('./App') 等价写法 import App from './App'

module.exports={ 'a':a } 等价写法 export default { a }

注意export default 后面接的是对象就可以了,并不是一定要写成{}的形式。

在es6中的json,如果键和值是一样的,可以直接省略键,比如:{’a‘:a} ==== {a},两个是等价的,因为import 和export是es6的写法,所以写为了{a}的形式

正常情况下你只要使用上面和require等价的用法就可以了,但是为了更方便理解别人的代码,需要看懂以下代码,

es6中是使用export命令后面加上要导出的变量来实现导出的,而用import xx from 'xxxx'的形式来引入,而xx必须跟export命令后的变量名一模一样才能导入

上面和require、module.exports等价的用法使用了default关键字,所以在引入时可以自定义名字,这也是最常用的用法

d.js
export var a ='js';

export function add(a,b){
    return a+b;
}
e.js
//注意下面两种引入方式中的变量名都需要跟d.js中的export 后的变量名保持一致

import {a,add} from './temp';

//也可以像下面一样分开写
import a from './temp'
import add from './temp' 
模块化总结

上一节我们说过js模块化是有减少http请求、减少script阻塞、减少全区变量污染、方便合作开发等一大堆优点,所以现代web开发都比较倾向于使用模块化,而无论是node.js还是es6提供的模块化功能都不能直接使用,浏览器是没有办法识别node.js环境的变量也无法识别es6的语法,所以我们需要用webpack来实现,把它们都转化成浏览器能识别的代码

在使用webpack的时候,我们还是用es6的import和export比较多,因为require js文件直接可以用es6替代,引入node_module我们只要通过配置wepack也可以轻松用es6替代,而node.js的核心模块在浏览器中本来就没法用,所以在前端代码中也用不上,所以只要用es6的语法来实现模块化就好啦

但是webpack重写了node的require方法,使用require可以引入其它资源文件,比如图片和.json文件等,这个时候我们还是需要使用require的

demo3 配置webpack

webpack基本配置

前面说过在命令窗口输入:webpack a.js b.js 就能吧a.js和它所依赖的所有文件都打包生成b.js,但是往往我们在真的使用webpack的时候并不会这么简单,所以webpack提供了配置文件

当你输入webpack命令时,webpack会自动去寻找当前路径下的webpack.config.js文件,在这个文件里webpack提供了很多配置项让你实现丰富的功能,下面演示一个基本的配置结构,请参照这注释理解每个配置项,具体的用法再开一篇文章来写

//前面说到过,直接require的是node的核心模块
const path = require('path');

//html-webpack-plugin是一个需要自己通过npm安装的模块
const HtmlWebpackPlugin = require('html-webpack-plugin');


var config = {
    // entry:定义要被打包的文件,可以是一个或者多个
    entry: {
        app:'./main.js'
    },
    // output:定义要打包后生成的文件
    output: {
        //定义生成的文件
        fileName: '[name].js',
        //定义生成文件的路径
        path: './dist',
        //定义引用文件的路径,(实际项目中,因为编译生成的文件很有可能被你拷贝到的网站路径和现在生成的路径不一致)
        publicPath: '/'
    },
    //resolve:定义能够被打包的文件,文件后缀名
    resolve: {
        //extensions配置的是可以省略的文件名类型,如果引用一个文件并没有加文件名,会去自动寻找以下配置的文件名
        extensions: ['.js', '.vue', '.json'],
        //为一个常用路径取一个别名,以后就不用写src的路径了,直接用@替代,它就会自动变成的绝对路径,如果resolve有多个参数,就是把参数拼接起来后然后取它们的绝对路径
        alias: {
            '@': path.resolve('src'),
        }
    },
    //module: webpack将所有资源都看做是模块,而模块就需要加载器;
    module: {
        //模块加载规则,比如es6语法的js文件浏览器是无法识别的,我们就需要使用babel-loader帮忙转化成es5的文件
        rules: [
          {
            //用正则表达式表示要匹配的文件,这里表示的是后缀为.vue的文件
            test: /\.vue$/,
            //loader都是需要通过安装或者自己写的,不是随便写一个文件名就可以的
            loader: 'vue-loader',
          },
          {
            test: /\.js$/,
            loader: 'babel-loader',
            //表示需要为哪些目录下的.js文件使用babel-loader做处理
            include: [path.resolve('src'), path.resolve('test')]
          }
        ]
    },
    //plugins:定义额外的插件,插件可以做到load做不到的事情,一般load只是辅助转化一个文件,把文件中浏览器不支持的部分转化成浏览器认识的
    plugins: [
        //会自动帮你生成一个index.html,并在html文件中引入打包生成的js文件
        new HtmlWebpackPlugin({
          filename: 'index.html'
        })
    ]
};

module.exports = config;

hk
680 声望111 粉丝

不断学习,等待时间的回报!