webpack4搭建項目環境

webpack發展史

  1. 在沒有ajax和jQuery之前,前端是不存在打包這個說法的,js沒有大規模使用,只做簡單的時鐘、mp3等效果,直接弄一個js文件引入就行
  2. 之後,人們開始使用iframe和flash等於服務器通信,因爲這兩種方式太過於tricky(棘手),直到google退出gmail的時候,人們發現了XMLHttpRequest,也就是AJAX,從此開始,前端出現了jquery等各種插件和庫,js文件越來越多。
  3. 隨着js做的事越來越多,js文件越來越大,U管理費用US等js文件壓縮合並工具陸續誕生,但是也有很多問題,比如:
    1. 庫和插件爲了要給他人調用,肯定要找個地方註冊,一般就是在 window 下申明一個全局的函數或對象。難保哪天用的兩個庫在全局用同樣的名字,那就衝突了
    2. 庫和插件如果還依賴其他的庫和插件,就要告知使用人,需要先引哪些依賴庫,那些依賴庫也有自己的依賴庫的話,就要先引依賴庫的依賴庫,以此類推
  4. 2009年,後端js發展,人們提出了CommonJS模塊化規範,也就是exports和require語法。但是它並不適用於瀏覽器,require是同步的,堵塞js腳本的執行,所以人們基於CommonJS定義了AMD規範(2011年),使用異步回調的語法來並行下載多個依賴項,也就是define函數(必須返回值)。現在出了ES6,7之後,已經差不多淘汰AMD了
  5. 2012年,webpack誕生,Browserify同期誕生,但webpack比它的優點:多文件打包、可以關心所有文件的打包、對資源文件的加載支持完善、支持CommonJS、AMD和ES6模塊規範等

簡單搞一個SPA應用

頁面搭建

  1. 接下來我們簡單搞一個SPA應用,弄一個webpack配置
  2. 首先,本地肯定需要安裝nodeJS,因爲webpack是基於nodeJS的
  3. 然後,搭建項目目錄,npm init初始化package.json文件,曬一下目錄結構
     
    ├─ .eslintrc.js
    ├─ .gitignore
    ├─ dist
    │    ├─ index.html
    │    ├─ index.js
    │    └─ index.js.map
    ├─ index.html
    ├─ package-lock.json
    ├─ package.json
    ├─ src
    │    ├─ index.js
    │    ├─ router.js
    │    ├─ untils
    │    │    └─ axios.js
    │    └─ views
    │           ├─ about
    │           └─ home
    └─ webpack.config.js
  4. 接下來,需要安裝依賴的包了
    1. 先安裝eslint進行語法檢查  
      npm install eslint eslint-config-enough babel-eslint eslint-loader --save-dev
      
      // package.json裏面插入如下配置
      "eslintConfig": {
          "extends": "enough",
          "env": {
            "browser": true,
            "node": true
          }
        }

       

  5. 然後編輯index.html文件
     
    <!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>首頁</title>
    </head>
    <body>
      <div id="app">首頁</div>
    </body>
    </html>
  6. 編輯src下的index.js文件
     
    // 引入 router
    import router from './router'
    
    // 啓動 router
    router.start()
  7. 編輯src下的router.js文件
     
    // 引入頁面文件
    import home from './views/home'
    import about from './views/about'
    
    const routes = {
      '/home': home,
      '/about': about
    }
    
    // Router 類,用來控制頁面根據當前 URL 切換
    class Router {
      start () {
        // 點擊瀏覽器後退 / 前進按鈕時會觸發 window.onpopstate 事件,我們在這時切換到相應頁面
        // https://developer.mozilla.org/en-US/docs/Web/Events/popstate
        window.addEventListener('popstate', () => {
          this.load(window.location.pathname)
        })
    
        // 打開頁面時加載當前頁面
        this.load(window.location.pathname)
      }
    
      // 前往 path,變更地址欄 URL,並加載相應頁面
      go (path) {
        // 變更地址欄 URL
        window.history.pushState({}, '', path)
        // 加載頁面
        this.load(path)
      }
    
      // 加載 path 路徑的頁面
      load (path) {
        // 首頁
        if (path === '/') path = '/home'
        // 創建頁面實例
        const view = new routes[path]()
        // 調用頁面方法,把頁面加載到 document.body 中
        view.mount(document.body)
      }
    }
    
    // 導出 router 實例
    export default new Router()
    

     

  8. 然後在src的view下建立home和about兩個文件夾,並在倆文件夾下新建index.js文件和style.css文件
     
    // home下的index.js舉例
    // 引入 router
    import router from '../../router'
    
    // 引入 html 模板,會被作爲字符串引入
    import template from '../../../index.html'
    
    // 引入 css, 會生成 <style> 塊插入到 <head> 頭中
    import './style.css'
    
    // 導出類
    export default class {
      mount (container) {
        document.title = 'foo'
        container.innerHTML = template
        container.querySelector('#app').addEventListener('click', () => {
          // 調用 router.go 方法加載 /bar 頁面
          router.go('/about')
        })
      }
    }

     

webpack配置

  1. 安裝webpack和它的插件 

    npm install webpack webpack-cli webpack-serve html-webpack-plugin html-loader css-loader style-loader file-loader url-loader --save-dev
  2. 安裝babel以支持打包生成ES5  
     
    npm install @babel/core @babel/preset-env babel-loader --save-dev
  3. 在package.json裏面添加配置項
     
    "babel": {
        "presets": ["env"]
      }
  4. 配置webpack
     
    const { resolve } = require('path')
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    const history = require('connect-history-api-fallback')
    const convert = require('koa-connect')
    
    // 使用 WEBPACK_SERVE 環境變量檢測當前是否是在 webpack-server 啓動的開發環境中
    const dev = Boolean(process.env.WEBPACK_SERVE)
    
    module.exports = {
      /*
      webpack 執行模式
      development:開發環境,它會在配置文件中插入調試相關的選項,比如 moduleId 使用文件路徑方便調試
      production:生產環境,webpack 會將代碼做壓縮等優化
      */
      mode: dev ? 'development' : 'production',
    
      /*
      配置 source map
      開發模式下使用 cheap-module-eval-source-map, 生成的 source map 能和源碼每行對應,方便打斷點調試
      生產模式下使用 hidden-source-map, 生成獨立的 source map 文件,並且不在 js 文件中插入 source map 路徑,用於在 error report 工具中查看 (比如 Sentry)
      */
      devtool: dev ? 'cheap-module-eval-source-map' : 'hidden-source-map',
    
      // 配置頁面入口 js 文件
      entry: './src/index.js',
    
      // 配置打包輸出相關
      output: {
        // 打包輸出目錄
        path: resolve(__dirname, 'dist'),
    
        // 入口 js 的打包輸出文件名
        filename: 'index.js'
      },
    
      module: {
        /*
        配置各種類型文件的加載器,稱之爲 loader
        webpack 當遇到 import ... 時,會調用這裏配置的 loader 對引用的文件進行編譯
        */
        rules: [
          {
            /*
            使用 babel 編譯 ES6 / ES7 / ES8 爲 ES5 代碼
            使用正則表達式匹配後綴名爲 .js 的文件
            */
            test: /\.js$/,
    
            // 排除 node_modules 目錄下的文件,npm 安裝的包不需要編譯
            exclude: /node_modules/,
    
            /*
            use 指定該文件的 loader, 值可以是字符串或者數組。
            這裏先使用 eslint-loader 處理,返回的結果交給 babel-loader 處理。loader 的處理順序是從最後一個到第一個。
            eslint-loader 用來檢查代碼,如果有錯誤,編譯的時候會報錯。
            babel-loader 用來編譯 js 文件。
            */
            use: ['babel-loader', 'eslint-loader']
          },
    
          {
            // 匹配 html 文件
            test: /\.html$/,
            /*
            使用 html-loader, 將 html 內容存爲 js 字符串,比如當遇到
            import htmlString from './template.html';
            template.html 的文件內容會被轉成一個 js 字符串,合併到 js 文件裏。
            */
            use: 'html-loader'
          },
    
          {
            // 匹配 css 文件
            test: /\.css$/,
    
            /*
            先使用 css-loader 處理,返回的結果交給 style-loader 處理。
            css-loader 將 css 內容存爲 js 字符串,並且會把 background, @font-face 等引用的圖片,
            字體文件交給指定的 loader 打包,類似上面的 html-loader, 用什麼 loader 同樣在 loaders 對象中定義,等會下面就會看到。
            */
            use: ['style-loader', 'css-loader']
          },
    
          {
            /*
            匹配各種格式的圖片和字體文件
            上面 html-loader 會把 html 中 <img> 標籤的圖片解析出來,文件名匹配到這裏的 test 的正則表達式,
            css-loader 引用的圖片和字體同樣會匹配到這裏的 test 條件
            */
            test: /\.(png|jpg|jpeg|gif|eot|ttf|woff|woff2|svg|svgz)(\?.+)?$/,
    
            /*
            使用 url-loader, 它接受一個 limit 參數,單位爲字節(byte)
    
            當文件體積小於 limit 時,url-loader 把文件轉爲 Data URI 的格式內聯到引用的地方
            當文件大於 limit 時,url-loader 會調用 file-loader, 把文件儲存到輸出目錄,並把引用的文件路徑改寫成輸出後的路徑
    
            比如 views/foo/index.html 中
            <img src="smallpic.png">
            會被編譯成
            <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAA...">
    
            而
            <img src="largepic.png">
            會被編譯成
            <img src="/f78661bef717cf2cc2c2e5158f196384.png">
            */
            use: [
              {
                loader: 'url-loader',
                options: {
                  limit: 10000
                }
              }
            ]
          }
        ]
      },
    
      /*
      配置 webpack 插件
      plugin 和 loader 的區別是,loader 是在 import 時根據不同的文件名,匹配不同的 loader 對這個文件做處理,
      而 plugin, 關注的不是文件的格式,而是在編譯的各個階段,會觸發不同的事件,讓你可以干預每個編譯階段。
      */
      plugins: [
        /*
        html-webpack-plugin 用來打包入口 html 文件
        entry 配置的入口是 js 文件,webpack 以 js 文件爲入口,遇到 import, 用配置的 loader 加載引入文件
        但作爲瀏覽器打開的入口 html, 是引用入口 js 的文件,它在整個編譯過程的外面,
        所以,我們需要 html-webpack-plugin 來打包作爲入口的 html 文件
        */
        new HtmlWebpackPlugin({
          /*
          template 參數指定入口 html 文件路徑,插件會把這個文件交給 webpack 去編譯,
          webpack 按照正常流程,找到 loaders 中 test 條件匹配的 loader 來編譯,那麼這裏 html-loader 就是匹配的 loader
          html-loader 編譯後產生的字符串,會由 html-webpack-plugin 儲存爲 html 文件到輸出目錄,默認文件名爲 index.html
          可以通過 filename 參數指定輸出的文件名
          html-webpack-plugin 也可以不指定 template 參數,它會使用默認的 html 模板。
          */
          template: './src/index.html',
    
          /*
          因爲和 webpack 4 的兼容性問題,chunksSortMode 參數需要設置爲 none
          https://github.com/jantimon/html-webpack-plugin/issues/870
          */
          chunksSortMode: 'none'
        })
      ]
    }
    
    /*
    配置開發時用的服務器,讓你可以用 http://127.0.0.1:8080/ 這樣的 url 打開頁面來調試
    並且帶有熱更新的功能,打代碼時保存一下文件,瀏覽器會自動刷新。比 nginx 方便很多
    如果是修改 css, 甚至不需要刷新頁面,直接生效。這讓像彈框這種需要點擊交互後纔會出來的東西調試起來方便很多。
    
    因爲 webpack-cli 無法正確識別 serve 選項,使用 webpack-cli 執行打包時會報錯。
    因此我們在這裏判斷一下,僅當使用 webpack-serve 時插入 serve 選項。
    issue:https://github.com/webpack-contrib/webpack-serve/issues/19
    */
    if (dev) {
      module.exports.serve = {
        // 配置監聽端口,默認值 8080
        port: 8080,
    
        // add: 用來給服務器的 koa 實例注入 middleware 增加功能
        add: app => {
          /*
          配置 SPA 入口
    
          SPA 的入口是一個統一的 html 文件,比如
          http://localhost:8080/foo
          我們要返回給它
          http://localhost:8080/index.html
          這個文件
          */
          app.use(convert(history()))
        }
      }
    }

     

  5. 啓動和打包
    1. 用命令行啓動  node_modules\.bin\webpack-serve webpack.config.js
    2. 用命令行打包  /node_modules/.bin/webpack-cli
  6. script配置
    1. 每次都寫那麼多有點麻煩,用script快捷命令入口啓動,在package.json裏面配置
      "scripts": {
          "dev": "webpack-serve webpack.config.js",
          "build": "webpack-cli"
        }

進階配置

  1. 以上就完成了簡單的配置,但是還是有很多點可以優化,比如 設置靜態資源的 url 路徑前綴、各個頁面分開打包、第三方庫和業務代碼分開打包、輸出的 entry 文件加上 hash、開發環境關閉 performance.hints、配置 favicon、開發環境允許其他電腦訪問、打包時自定義部分參數、webpack-serve 處理路徑帶後綴名的文件的特殊規則、代碼中插入環境變量、簡化 import 路徑、優化 babel 編譯後的代碼性能、使用 webpack 自帶的 ES6 模塊處理功能、使用 autoprefixer 自動創建 css 的 vendor prefixes

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章