本文关键字:exports module.exports import export 模块化 node变量 require webpack配置项
若是你对以上关键字有一些疑问,可能这篇文章会对你有一点点帮助,文章主要为你比较深刻的解释了node和es6中的模块化,最后列出了一个基本的webpack配置,并对每项作出了说明 css
若是你有疑问能够加qq群和小伙伴一块儿讨论,群号:613202492html
虽然本人比较懒,可是此次会迅速出下一篇博客的,名字暂定为:跟着vue官方学webpack配置前端
webpack是一个模块打包工具;JavaScript中一个单一功能的方法(函数),咱们就能够把它叫作一个模块;demo1演示如何把代码模块化,并用webpack是如何来打包这些模块的。vue
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>
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把模块化的代码打包。node
由于须要经过npm安装webpack,因此要先下载node.js并安装。 node官方下载地址 点左边那个版本下载就行了。jquery
安装好了node.js就可使用npm了,它是一个包管理器,最重要的功能就是帮你下载(管理)各类包,所谓包就是具备完整功能的一组或者一个js文件,一般一个包是多个模块构成的,你能够简单理解为小功能叫模块,多个模块构成一个大的功能叫包。webpack
在命令行中输入: npm install webpack -g
就能够全局安装webpack了,若是网比较慢,可使用淘宝的cnpm镜像(npm 下载源都是国外的,网络慢点,cnpm使用的是国内的镜像,因此快点)。es6
在命令行中输入: npm install -g cnpm --registry=https://registry.npm.taobao.org
就可使用淘宝的镜像了,安装webpack就可使用 cnpm install webpack -g
web
cd demo1
webpack c.js d.js
正则表达式
生成的d.js 就是咱们须要生成的代码,简单的说其实就是把几个文件合并成一个文件了,这样作有一个很大的好处:
当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')
模块化本质上来讲解决的问题并很少,主要就是依赖和冲突问题,可是衍生的好处很是多,好比合并两个项目更容易了,分工合做更简单了,一个函数两百行的代码愈来愈少了,要知道这些问题在不少场景下在过去都是挺头疼的一件事
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能够引用模块或者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了。
前面说过能够经过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};
除了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的
前面说过在命令窗口输入: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;