學習webpack的前戲

寫在前面

本文關鍵字:exports module.exports import export 模塊化 node變量 require webpack配置項
若是你對以上關鍵字有一些疑問,可能這篇文章會對你有一點點幫助,文章主要爲你比較深刻的解釋了node和es6中的模塊化,最後列出了一個基本的webpack配置,並對每項作出了說明 css

若是你有疑問能夠加qq羣和小夥伴一塊兒討論,羣號:613202492html

雖然本人比較懶,可是此次會迅速出下一篇博客的,名字暫定爲:跟着vue官方學webpack配置前端

webpack基本概念

demo1 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>
模塊化後的代碼(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

安裝node.js

由於須要經過npm安裝webpack,因此要先下載node.js並安裝。 node官方下載地址 點左邊那個版本下載就行了。jquery

安裝webpack

安裝好了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

使用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;
相關文章
相關標籤/搜索