( 開篇 )仿寫'Vue生態'系列___'你webpack溜麼?'

( 開篇 )仿寫'Vue生態'系列___'你webpack溜麼?'

關於這個系列 css

做者離職深造也有一個月了, 前端相關的視頻與資料學了很是多, 本身感受到如今的知識之間只是呈現出一種相互之間的弱聯繫, 也就是還不成'體系', 每個知識點我都學過我都會用, 可是統一塊兒來就有些地方不是很明朗了,相信不少前端仔也會有一樣的感覺.前端

好比說:
1: 你知道你常常用的插件的原理麼? 🤔
2: 你能寫出一個與他功能相仿的插件給別人去使用麼? 🤷‍♀️
3: 不借助插件你能正常開發麼? 🕊
4: 面試時候面試官問你,某些技術的原理, 你是否只能回答"我只是站在巨人的肩膀上?"┑( ̄Д  ̄)┍vue

爲了把這些'圍繞着框架'展開的一系列知識弄懂, 因此本次與你們一塊兒從0開始, 寫一套簡易版的vue框架, 不但要寫框架, 咱們還要寫 vuex, vue-router, axios, 簡易的webpack, webpack-loader, webpack-plugin等等, 涉及到的知識點都會拿出來詳談一番, 若是有哪裏沒有討論到位的咱們能夠私信繼續死磕, 本次項目會在github上同步更新 githubnode

本人去年至今年年初在公司一直負責面試前端, 因此文章中不但涉及技術, 中間還會穿插着個人理解與以前作過的筆記, 以及面試時候我遇到的一些問題, 畢竟再過一個月或者兩個月我也就結束'閉關'去找工做了, 因此對面試這方面也會有所討論, 相關知識與問題 你們均可以一塊兒交流, 共同窗習,共同進步, 早日實現自我價值!!react

一. 前端與webpack

(本次工程師基於webpack4)
webpack如今已經能夠說是前端必會的技能了, 若是有人跟你說, 那東西看看文檔就行不用學, 那他就是在害你, webpack是一門須要手熟的技術, 最基本的結構你必須不看資料能純手打出來, 如今框架都幫咱們集成好了開發環境, 從另外一方面來講也會溫水煮青蛙麻痹咱們的神經, '只要會用就能夠了'並不適合webpack, 這門技術能夠幫咱們更好的瞭解前端這個職位的定義, 工欲善其事必先利其器, 那麼本期就從webpack的配置開始吧!webpack

需求分析
1: 本次實現的簡易框架 暫定名字爲 'C', 開發使用class的形式, 因此須要支持es6
2: 能夠加載css文件, 由於有了樣式, 寫代碼纔有樂趣
3: 熱更新
4: 區分生產環境與開發環境, 就是爲了鞏固webpack優化的相關知識ios

基礎的搭建--逐行解析!

1: 建立一個dist文件夾存放打包文件
2: 建立一個src文件夾存放開發信息
3: src裏面index.js 主要的導出文件
4: 安裝依賴 npm install webpack webpack-cli -Dgit

下面這段最基礎的代碼你能不看資料本身手敲麼
標題的'溜'不是指多麼的深刻, 而是能把最基礎的東西準確的作出來.es6

const path = require('path');
module.exports = {
   mode:'development',
   entry:{
    main:'./src/index.js'
   },
   output:{
     filename: '[name].js',
     path: path.resolve(__dirname, '../dist')
   }
}

知識點彙總( 死磕知識點... )github

一: path

  1. node的原生自帶模塊.
  2. 與其一同出現的有 resolve join 兩個方法
    join:
    windows下文件路徑分隔符使用的是"", Linux下文件路徑分隔符使用的是"/", path.join() 方法使用平臺特定的分隔符把所有給定的 path 片斷鏈接到一塊兒,並規範化生成的路徑。長度爲零的 path 片斷會被忽略。 若是鏈接後的路徑字符串是一個長度爲零的字符串,則返回 '.',表示當前工做目錄.
    onsole.log(path.join('a','b','c','..','d')) 打印 a/b/d 被拼接爲一體, ..把c幹掉了;
    resolve:
    方法會把一個路徑或路徑片斷的序列解析爲一個絕對路徑, 若是沒有傳入 path 片斷,則 path.resolve() 會返回當前工做目錄的'絕對路徑'。
    console.log(path.resolve('a','b','c')) /Users/lulu/web/cc_vue/a/b/c 精確到了工程

二: module.exports
node的模塊導出方式, 爲何他是用'.'連接, 而咱們使用的es6 是' '空格,好比:export default
緣由: 學過node的朋友會很好理解, node能夠隨時的讀取文件, 而module只是個變量而已, exports是module上面的一個屬性, 每次讀取文件以後, 用戶想要輸出的文件掛載到這個對象上行了, 因此它使用'.'的這種方式, 實則是在操做對象. 而es6的語法在瀏覽器上是實現不了的, 須要編譯以後再執行, 它採用的是關鍵字模式, 好比我來編寫一個打包插件來打包export default這種語法, 我須要先解析出語法樹, 而後把它的文件絕對路徑轉換爲key, 內容是value的一個閉包(這方面後面會作一下), 因此es6的引入方式纔是同步, 沒法隨處的寫;

三: mode:webpack新增的模式屬性

第一. none 不使用webpack自帶的模式
第二. development : 開發模式

1. 不壓縮
 2. 不搖樹(爲何開發環境不'搖樹'? 緣由是若是去除了多餘的代碼, 那麼調試的時候就找不到準確的段落了;)
 3. 有完善的錯誤提示
 4. 能夠設置完備的source-map
// 添加了下面的兩句
plugins: [
   // 當開啓 HMR 的時候使用該插件會顯示模塊的相對路徑,而不是文件的id。
   new webpack.NamedModulesPlugin(),
   // 更改全局的環境變量
   new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("development") }),
      ]

第三. production 線上模式

// 自動添加
   plugins: [
   // 壓縮js代碼
    new UglifyJsPlugin(/* ... */),
   // 設定環境變量
    new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("production") }),
   // 過去 webpack 打包時的一個取捨是將 bundle 中各個模塊單獨打包成閉包。這些打包函數使你的 JavaScript 在瀏覽器中處理的更慢。相比之下,這個插件 能夠提高或者預編譯全部模塊到一個閉包中,提高你的代碼在瀏覽器中的執行速度。
    new webpack.optimize.ModuleConcatenationPlugin(),
// 在編譯出現錯誤時,使用 NoEmitOnErrorsPlugin 來跳過輸出階段。這樣能夠確保輸出資源不會包含錯誤。對於全部資源,統計資料(stat)的 emitted 標識都是 false。
    new webpack.NoEmitOnErrorsPlugin()
]

四: entry

  1. 直接寫字符串 entry:./xx/index 此時默認名爲main
  2. 多入口文件寫數組[./xx1/index, ./xx1/index]
  3. 對象, 也是官方推薦的寫法, 每一個入口都定義名字, 配合打包輸出使用
entry :{
  main:'./src/index.js',
  beas:'./src/base.js'
}

五: output 這個比較單一

// 這裏能夠自定義名字, 就是'寫死'就行
// 下面這種方式意思就是 上面entry的名字是啥, 這裏就是啥
// 通常多入口打包都這麼寫
  filename: '[name].js',
// 這個形式結合上面的知識點確定都沒問題了, 
// __dirname: 當前文件所在文件夾
  path: path.resolve(__dirname, '../dist')

看了這些感覺如何??? 雖就這麼幾行代碼, 但也要死磕一下

babel相關

npm install babel-loader -D
module裏面的rules選項是專門玩loader的
只有一個key, 還寫成對象形式, 估計之後會有所動做

module: {
    rules: [
      {
        test: /.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader?cacheDirectory=true'
      }
    ]
  }
  1. test 也就是指定的匹配規則, 後面有機會的話 我講一下正則相關的技巧
  2. exclude 不處理的文件,也就是說依賴所有不翻譯, 不加這句會很慢的
  3. loader也就是具體用哪一種方式處理匹配出來的文件內容, 上面這種寫法是官網推薦的寫法, 默認值爲 false。當有設置時,指定的目錄將用來緩存 loader 的執行結果。以後的 webpack 構建,將會嘗試讀取緩存,來避免在每次執行時,可能產生的、高性能消耗的 Babel 從新編譯過程

babel-loader幹麼的?
看過他的源碼才知道, 他是個領導, 負責安排專門的人來執行編譯工做, 而他本身只負責調度與輸出.

核心員工"@babel/core"
npm install @babel/core -D
他的命名式7.x 開始規範下來的, 有的人可能見過'-'連接的, 都是早期的版本了, 由於他只是一個執行具體動做的模塊而已, 因此須要@babel開頭
實際上, 具體的工做都是這個模塊去完成的, 因此你只加載babel-loader是沒有用的.
其實babel團隊這樣設計纔是符合設計模式的, webpack也分出 webpack-cli, 如今不少插件都不是單一一個文件了.

.babelrc
webpack的配置項實在是太多了, 若是都集中在一個文件裏面就不符合設計原則了, 像這種很經常使用, 並且配置項比較多的, 會被單獨抽離出來配置, babelrc(和.babelrc.js/ package.json#babel)文件名 均可以

@babel/preset-env 指定目標的需求🤔
npm install @babel/preset-env -D
babel轉換js代碼的功能其實能夠很細化, 好比說有的人須要所有轉爲能夠兼容 ie7的語法, 而我平時轉換爲 Chrome 最近5個版本 能兼容的就能夠, 因此不必轉換太多致使性能佔用,

就要在.babelrc這個文件裏面 設置在 presets選項下
指定我到底要解析到什麼地步
他有一個對照表, 把配置項合併計算出交叉配置

"presets": [
    [
      "@babel/preset-env",
      {
        // 寫法1
        "targets": {
            // 用戶佔比大於1%的瀏覽器的 最後兩個版本
          "browsers": ["> 1%", "last 2 versions"]
        }
        // 寫法2
        "targets": {
          "chrome": "58",
          "ie": "11"
         }
      }
    ]
  ],

下面這種形式: 若是不進行任何配置, preset 所包含的插件將支持全部最新的 JavaScript (ES201五、ES2016 等)特性。

"presets": ["@babel/preset-env"],

官方維護的類型
@babel/preset-env
@babel/preset-flow
@babel/preset-react
@babel/preset-typescript
Stage 0 - 設想(Strawman):只是一個想法,可能有 Babel插件。
Stage 1 - 建議(Proposal):這是值得跟進的。
Stage 2 - 草案(Draft):初始規範。
Stage 3 - 候選(Candidate):完成規範並在瀏覽器上初步實現。
Stage 4 - 完成(Finished):將添加到下一個年度版本發佈中。
preset-es2015 等等 被@babel/preset-env取代, babel7版本

上面咱們只是作到了好比說 箭頭函數這種轉換, 可是像promise這種實例方法是沒有的, babel分工很明確, 上面的配置只作這方面的轉換

霸道的polyfill
運行時的庫, 因此要是save!!!!
npm install --save @babel/polyfill
這個庫將會模擬一個徹底的 ES2015+ 的環境。
使用就是取對應的頁面上require他
爲何說他霸道?
這傢伙就是個鐵憨憨, 一股腦的把全部配置都注入進來, 有了它, 打包後的體積大的嚇人, 但他也是最全面的, 致使我沒用到的功能他都給我寫好了, 那就很是沒有必要了, 因此babel給了配置項, 能夠只注入咱們用到了的類與方法, 這樣體積會減少不少.
仍是要交給'需求'去作.

"presets": [
   ["@babel/preset-env",{
        "useBuiltIns":"usage"  // 使用polyfill了時候只加載使用到的方法
    }]
 ]

他還有一個致命的缺點, 就是污染全局, 好比說我開發一個插件給你用, 我就把你的全局變量都改爲最新版的了, 開發插件的準則就是儘可能不要動用戶的數據, 萬一用戶也引入了另外一個版本的他, 那就太可怕了, 因此開發插件babel提供了下面的方案

@babel/plugin-transform-runtime
plugin-transform-runtime 已經默認包括了 @babel/polyfill,所以不用在獨立引入。
npm install @babel/plugin-transform-runtime -D
他也算是個分配至, 須要安裝一個幹活的
具體代碼注入都是在他文件裏面取出來的
npm install --save @babel/runtime

避免屢次編譯出helper函數:
Babel轉移後的代碼想要實現和原來代碼同樣的功能須要藉助一些幫助函數
@babel/runtime包就聲明瞭全部須要用到的幫助函數,而@babel/plugin-transform-runtime的做用就是將全部須要helper函數的文件,依賴@babel/runtime包

不會污染全局變量
屢次使用只會打包一次
依賴統一按需引入,無重複引入,無多餘引入

// 缺點
不支持實例化的方法Array.includes(x) 就不能轉化, 由於若是實現了這個就是污染全局了啊
若是使用的API用的次數不是不少,那麼transform-runtime 引入polyfill的包會比不是transform-runtime 時大

這個要在.babelrc文件夾,plugins選項裏面配置

"plugins": [
    [
      "@babel/plugin-transform-runtime",
      {
        "absoluteRuntime": false,
        // 若是配置他爲2, false不用
        // 需安裝 @babel/runtime-corejs2
        // 則不用安裝 @babel/runtime
        // 做用: promise 代碼變成了一個獨立的變量 _promise,不會影響全局的 Promise。
        "corejs": 2,
        // 默認就是true  寫出來讓你們知曉
        "regenerator": true, // 切換生成器函數是否轉換爲使用不會污染全局範圍的再生器運行時。
      }
    ]
  ]

上面的配置好了, 就能夠正常玩耍es6了, 若是上面的你全都看完了, 那你還怕之後別人問你這方面知識麼??

css樣式方面

開篇也說了, 有個好的造型, 代碼寫起來也愉悅

module: { // 只有一個key, 還寫成value, 估計之後會有所動做

    rules: [
      {
        test: /.(css|sass|scss|less)$/,
        use: ['style-loader', 'css-loader', 'postcss-loader']
      }
    ]
  }

配置大同小異, 我來具體解釋下每個loader

  1. style-loader: 使用<style>標籤將css-loader內部樣式注入到咱們的HTML頁面
  2. css-loader: 主要用於處理圖片路徑 與 導入css文件的路徑 而後義字符串的形式存入js
  3. postcss-loader: 爲css樣式加上必要的前綴
    他須要配合autoprefixer使用, npm install -autoprefixerD, 頗有必要的優秀軟件
    與.babelrc相似的配置文件以下, 名稱爲
// 他須要導出一下, 不想.babelrc直接讀取{}內的
module.exports = {
  plugins: [
    require('autoprefixer')({
        // 1: 最新版的話 browsers改爲overrideBrowserslist就不報錯了
        // 2: 部分用戶出現不寫[]裏面的內容就不編譯的bug
      overrideBrowserslist: [
          'iOS 7.1', 
          'ff > 31', 
          'ie >= 8',
          'Chrome > 31', 
          'Android 4.1', 
        ]
    })
  ]
};

遇到的知識都作了梳理, 工欲善其事必先利其器, 每個知識點都值得探究, 這邊寫的多了電腦就好卡, 下篇把剩下的幾個知識點說一下就能夠正式作框架了, 寫文章的過程也是對本身的一種審視, 把不少問題拿到面前質問本身到底會不會, 挺累的但也是收穫滿滿.

end
你們均可以一塊兒交流, 共同窗習,共同進步, 早日實現自我價值!!

github: github
我的技術博客: 我的技術博客

相關文章
相關標籤/搜索