媽媽再也不用擔心我不會webpack了2

尼古拉斯李三發表於2017-11-27

前言

之前寫了一篇媽媽再也不用擔心我不會webpack了,這次繼續對其進行補充。本文依舊是遵循直觀易懂的規則進行書寫。希望對大家有幫助。如果不太熟悉webpack可以先看看之前的文章媽媽再也不用擔心我不會webpack了。下面我們由淺入深來介紹webpack的使用

一些你不知道是什麼意思的東西(題外話)

import path = require('path');

  • path.join
  • path.resolve
  • __dirname

path其實是node中的一個模組,下面我們就將一下這幾個常見的東西。

path.join

path.join其實是對路徑進行拼接。

const path = require('path');

let str1 = path.join('./path/./', './upload', '/file', '123.jpg');
console.log(str1); // path/upload/file/123.jpg

let str2 = path.join('path', 'upload', 'file', '123.jpg');
console.log(str2); // path/upload/file/123.jpg複製程式碼

使用了它之後就得到了一個拼接好的路徑

path.resolve

它是絕對路徑的操作

let myPath = path.resolve('path1', 'path2', 'a');
console.log(myPath); // E:/workspace/NodeJS/path1/path2/a複製程式碼

它的結果是絕對路徑,不懂絕對路徑和相對路徑的同學去查下相關知識哦。
這部分到這就結束了,下面可能涉及到這幾個東西的使用。

__dirname

使用__dirname變數獲取當前模組檔案所在目錄的完整絕對路徑
因為下面可能會碰到這幾個東西,所以我們稍微簡單提了一下。題外話到此結束,我們現在進入正題。

resolve配置

resolve.extensions

我們在編輯器上開發專案程式碼,但這個傻慫編輯器IDE功能並不是很強,每次import進來的檔案都沒有字尾名。如果你使用的是腳手架工具,你可能會發現一個有趣的地方,就是當我們引入js、jsx或者vue檔案的時候,我們不需要加字尾就可以使用了。但是你寫了一些less或者sass引入到檔案中,也沒有新增字尾,編譯直接報錯。這個其實就是resolve的問題。

resolve: {
    // Add '.ts' and '.tsx' as resolvable extensions.
    extensions: [".ts", ".tsx", ".js", ".json"]
}複製程式碼

對resolve進行配置能設定模組如何被解析。
這裡的extensions就是字尾的使用,我這裡預設是ts/tsx/js/json,這些檔案在import時,不新增字尾是可以的,只需要在陣列中新增你想要省略的字尾名就可以達到同樣的效果。比如下面這種

extensions: ['.web.js', '.mjs', '.js', '.json', '.web.jsx', '.jsx', '.less']複製程式碼

現在就算編輯器不給你新增字尾,你也不需要再加上字尾了,是不是省了很多事。

resolve.alias

這裡是建立 import 或 require 的別名,來確保模組引入變得更簡單

resolve: {
    alias: {
      Utilities: path.resolve(__dirname, 'src/utilities/'),
      Temp: path.resolve(__dirname, 'src/templates/')
    }
}複製程式碼

之前你引入src/template裡面的1檔案可能是這樣

import 1 from '../src/template/1';複製程式碼

現在你可以這樣寫了

import 1 from 'Temp/1';複製程式碼

這裡的路徑還是具體看你的檔案路徑。不要照抄照搬哦。
這兩項是我個人覺得使用頻率比較多的,其他情況請去官方文件檢視下。

全域性變數的使用

使用全域性變數進行url的替換

現在專案還是在開發階段,你可能通過下面的介面獲取資訊

http://www.xxx.com/test/v3 + 具體介面複製程式碼

這個介面是放在測試伺服器上的,但專案一旦上線要使用線上伺服器

http://www.xxx.com/api/v4 + 具體介面複製程式碼

你於是冒出了一個很傻X的想法,本地開發或者測試時使用上面的介面,等到上線的時候再將它改掉。鬼鬼,我們不能這麼秀。我給你提供一個好方法。

使用DefinePlugin外掛來建立全域性變數來解決這個問題

DefinePlugin允許你建立一個在編譯時可以配置的全域性常量,我們下面建立一個名為url的全域性變數,如果你是將開發和生產環境的webpack配置檔案分開,你可以這樣寫

開發環境
plugins: [
    new webpack.DefinePlugin({
        url: JSON.stringify('http://www.xxx.com/test/v3')
    })
]複製程式碼
生產環境
plugins: [
    new webpack.DefinePlugin({
        url: JSON.stringify('http://www.xxx.com/api/v4')
    })
]複製程式碼

如果你只有一個webpack配置檔案,你也可以寫成這樣

plugins: [
    new webpack.DefinePlugin({
        url: process.env.NODE_ENV === 'production' ?
            JSON.stringify('http://www.xxx.com/api/v4') :
            JSON.stringify('http://www.xxx.com/test/v3')
    })
    // 這裡其實涉及到一個問題,就是你在生產環境的時候必須增加命令修改process.
    // env.NODE_ENV = production,否則上面的程式碼是不生效的
    // 在package.json的scripts物件中可以使用,使用方法見我上一篇webpack的文章
]複製程式碼

由於這個變數必須包含字串引號,所以你要麼使用'"你的變數內容"', 或者使用 JSON.stringify('你的變數內容')這種形式。
現在你在專案中的介面url就可以寫成這樣了

`${url}/介面資訊` // es6的字串模板應該都懂吧?複製程式碼

你console.log(url)也是可以的哦,現在我們就解決了這個噁心的問題。
但是還有更噁心的問題等著我們,哈哈。真滴煩!!!
如果你的專案是腳手架搭建,往往會有eslint,eslint不進行設定是沒辦法使用這個全域性變數的,找到eslint配置檔案,新增如下程式碼

"globals": {
    "go": true
}複製程式碼

多入口檔案打包

為什麼要設定多檔案入口打包

在很多情況下,我們都是用當下流行的框架進行web開發,比如vue、react。在開發一段時間過後,測試ok,我們準備build專案了,但是打包之後檔案是4.5MB,這玩意對pc或者移動來說都不是一個很好的體驗。如果我們不去管它,那每次我們改版或其他的一些情況,使用者都需要去重新載入4.5mb的檔案,哪怕你只是修改了一行程式碼。

多入口實現第三方庫檔案的單獨打包

使用多入口檔案配合外掛解決此問題

entry: {
    vendor: ['react', 'react-dom'],
    app: "./src/index.tsx"
}複製程式碼

這裡我們設定了兩個入口,一個是app,就是我們傳統使用的入口檔案。而vendor使用的是一個陣列,我們把react和react-dom單獨提取出來進行打包。這些庫我們基本上是不會改動原始碼的,如果我們把它們單獨打包出來,即便我們修改了專案的程式碼,react和react-dom的程式碼都不需要變,這時瀏覽器都直接讀取vendor檔案的快取就可以了,減少了每次載入資源的體積,增強了使用者體驗。

配合外掛CommonsChunkPlugin使用

只是增加入口檔案是不管用的,我們需要使用外掛把vendor檔案從app檔案中剝離出來

plugins: [
    new webpack.optimize.CommonsChunkPlugin('vendor')
]複製程式碼

現在我們已經把vendor和app檔案分割了。這裡只是舉了一個簡單的栗子,小夥伴可以根據自己的需求自己進行配置。

我們也可以把公共元件進行一個單獨的打包,這裡不再贅述,感興趣的小夥伴可以自己試驗哦。

html-webpack-plugin

之前的文章其實已經介紹過這個外掛了,但這次我們稍微具體的說一下。我們使用腳手架生成的index.html其實你是找不到script標籤引入js檔案的,可能你也想這樣做,自己動手引入真的麻煩。html-webpack-plugin來幫你

plugins: [
    // Generates an `index.html` file with the <script> injected.
    new HtmlWebpackPlugin({
      inject: true, // 這個配置項為true表示自動把打包出來的檔案通過自動生成script標籤新增到html中
      template: index.html, // 模板檔案,其實如果沒有特殊要求,可以考慮就是用原本的html檔案,不再單獨建立模板。
      minify: { // 壓縮的配置,感興趣的同學意義自己查下意思
        removeComments: true,
        collapseWhitespace: true,
        removeRedundantAttributes: true,
        useShortDoctype: true,
        removeEmptyAttributes: true,
        removeStyleLinkTypeAttributes: true,
        keepClosingSlash: true,
        minifyJS: true,
        minifyCSS: true,
        minifyURLs: true,
      },
    })
]複製程式碼

你的模板html檔案是這樣

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8" />
    <title>Hello React!</title>
</head>
<body>
<div id="example"></div>

<!-- Main -->
</body>
</html>複製程式碼

通過使用上面的html-webpack-plugin配置它就變成了這樣

<!DOCTYPE html><html><head><meta charset="UTF-8"/><title>Hello React!</title></head><body><div id="example"></div><script type="text/javascript" src="vendor.19786c9df38012fdca96.js"></script><script type="text/javascript" src="app.19786c9df38012fdca96.js"></script></body></html>複製程式碼

這樣其實就是和你用腳手架搭建的一毛一樣了。

講一些專案中會出現的問題--devServer的使用

我們在使用webpack時,常常會用到webpack-dev-server。它給我們提供了一個server,使我們的專案能跑server上。

historyApiFallback

現在專案在本地正常執行,你看了下位址列

localhost:8080/#複製程式碼

leader說#是什麼鬼?必須去掉,你見過誰的網址帶#?
這個#其實是hash路由進行路由跳轉的依託。它是可以去掉的。react4是使用BroswerRouter替換HashRouter即可,vue的話同學去查一下即可。去掉#之後我們使用的就是h5的history模式進行路由跳轉了。

你以為這樣就行了麼?

但是老闆又有新要求了,我們的頁面不是放在根目錄下。網址是這樣www.xxx.com/xxx,這個時候你要想頁面放在伺服器上能正常使用就需要在index.html新增base標籤。
你信心滿滿的進行試驗,發現,我擦,報錯了,連頁面都找不到了。


這個原因是因為現在在這個路徑下我們是找不到資原始檔,會報404的問題,這個時候我們必須要設定

devServer: {
    historyApiFallback: true
  }複製程式碼

這個東西就是告訴webpack-dev-server,再找不到檔案的時候預設指向index.html,底層的東西我並沒有去深入查詢,感興趣的同學可以去查下。

有些情況下,我們可能會起不止一個服務,這個時候埠往往就會衝突,新增port屬性,修改下埠即可解決衝突問題。

devServer: {
    historyApiFallback: true,
    port: 1234
  }複製程式碼

現在,地址就變成了localhost: 1234了。
devServer其實還有很多配置項,感興趣的同學可以去官網檢視

webpack的熱替換問題

熱替換俗稱HMR,即只更新你修改的區域性內容,而不重新整理整個頁面,大大提高開發效率。這個只能在開發時使用哦,下面簡單說一下用法。後面直接說react和vue的使用。

 const webpack = require('webpack');

  module.exports = {
    entry: {
      app: './src/index.js'
    },
    devServer: {
     hot: true
    },
    plugins: [
     new webpack.HotModuleReplacementPlugin()
    ]複製程式碼

下面是index.js檔案需要的配置(這裡覺得比較雞肋,因為如果你不把js全寫在一起,你就要每個檔案都要新增這個東東。)

+ if (module.hot) {
+   module.hot.accept('./app.js', function() {
+     console.log('Accepting the updated printMe module!');
+     printMe();
+   })
+ }複製程式碼

module.hot.accept 接受兩個引數,第一個引數是修改的檔案,第二個是會掉函式。這裡只要app.js修改,就會觸發回撥。

我們大多數情況下是使用react或者vue的,在這種情況下,你使用webpack的HMR是不起作用的。因為它無法儲存這些框架的狀態。
react和vue給我們提供瞭解決方案

  • react-hot-loader
  • vue-loader

    同學們可以在npm中搜尋這兩個東西的用法,都有對應的使用。

此部分webpack配置要去npm找到對應的loader看,很簡單。我主要講我當時很困惑的一點。
我主要講一下react的,就是在使用路由和redux的情況下,我們的index.js檔案應該怎麼寫。

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import { AppContainer} from 'react-hot-loader'
import registerServiceWorker from './registerServiceWorker';
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import reducers from "./reducer";
import "./index.css";
import App from "./App";
import './style/style';

// redux的配置,可以忽略
const store = createStore(reducers, compose(
    applyMiddleware(thunk),
    window.devToolsExtension ? window.devToolsExtension() : ()=>{}
));

registerServiceWorker();

使用AppContainer包裹根元件即可

const render = Component => {
  ReactDOM.render(
      <AppContainer>
          <Component store={store}/> 
      </AppContainer>,
      document.getElementById('root')
  )
};
render(App);
// App作為內容的根元件,redux和router全部放在裡面,只要有內容修改,就呼叫render函式
if (module.hot) {
    module.hot.accept('./App', () => { render(App) })
}複製程式碼

App檔案

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import { BrowserRouter, Switch, Route } from 'react-router-dom';
import { Provider } from 'react-redux';

import Second from "./second";
import Third from "./Third";
import Header from "./header";
import First from "./first";
class App extends Component {
  render() {
    return (
      <Provider store={this.props.store}>
          <BrowserRouter className="App">
              <div>
                  <Header />
                  <Switch>
                      <Route exact path="/" component={First}/>
                      <Route path="/second" component={Second}/>
                      <Route path="/third" component={Third}/>
                  </Switch>
              </div>
          </BrowserRouter>
      </Provider>
    );
  }
}

export default App;複製程式碼

我告你,現在你開發,根本不需要頁面重新整理,超爽的。修改哪裡,哪裡變化。謝謝大家

相關文章