快速配置 webpack 多入口腳手架

盧溝曉月pro發表於2018-12-27

背景

當我們基於vue開發單個專案時,我們會init一個vue-cli,但當我們想在其他專案裡共用這套模板時,就需要重新init一個,或者clone過來,這非常不方便,而且當多人開發時,我們希望所有的開發程式碼都在一個git目錄下,這時就有了對webpack進行配置的需求,當有些頁面需要多入口時,我們又產生了對多入口配置的需求,這裡提供一種配置方案,希望能幫助到有需要的人,廢話不多說,我們開始吧!

先初始化一個專案

我們通過vue init webpack demo 生成的檔案目錄是這樣的

快速配置 webpack 多入口腳手架

修改專案入口

要改多入口,首先改造一下webpack.base.conf.js中的contextentry

context:基礎目錄,絕對路徑,用於從配置中解析入口起點(entry point)和 loader。

entry:起點或是應用程式的起點入口。從這個起點開始,應用程式啟動執行。

module.exports = {
  context: path.resolve(__dirname, '../'),
  entry: {
    app: './src/main.js'
  },
};
複製程式碼

如果專案只有一個入口,那麼直接在這裡改entry就可以了,但一般我們都是多個專案在放一個目錄裡,所以要提取出來context和entry。

const paths = require('./paths')
const rootPath = paths.rootPath
module.exports = {
  context: rootPath
  entry: {
    app: utils.getEntry(),
  }, 
};
複製程式碼

在config裡新建_config.jspaths.js

_config.js,用於設定當前啟動專案,並將這個檔案新增到.gitignore中,因為以後多人開發都是在本地修改專案地址。

 'use strict'
 module.exports = {
  appName: 'mobile',
  projectName: 'demo'
}
複製程式碼

這裡設計2個目錄,appName是src下的一級目錄,projectName是appName下的二級目錄,目的在於方便擴充,比如公司的專案分為pc專案和mobile專案,開發時便於區分,如果你的專案比較少,那可以把appName寫成一個固定字串如:pages,每次切換專案只更改projectName就可以了。我們將所有專案放在src下,類似目錄如下

├─mobile
│  ├─demo
│  └─demo2
└─pc
    ├─demo
    └─demo2
複製程式碼

paths.js,用於配置一些全域性需要用到的路徑

'use strict'
const path = require('path')
const fs = require('fs')
const _config = require('./_config')

const rootPath = fs.realpathSync(process.cwd()) // 專案根目錄 fs.realpathSync表示獲取真實路徑
const resolve = relativePath => path.resolve(rootPath, relativePath) // 自定義一個resolve函式,拼接出需要的路徑地址
module.exports = {
  rootPath, // 專案根目錄
  commonPath: resolve('common'), // 公共目錄
  projectPath: resolve(`src/${_config.appName}/${_config.projectName}`), // 子專案根目錄
  config: resolve('config'), // 專案配置
  static: resolve('static') // 公共靜態資源目錄
}
複製程式碼

新建common資料夾

我們在src同級新建一個common資料夾,用於存放靜態資源及公共元件

-components 
  ├─assets
  ├─components
  └─xhr
複製程式碼

assets裡可以存放公共樣式css,公共字型font,公共圖片img,公共方法js等;components裡存放提取出來的公共元件,xhr我放的是axio的封裝,整個資料夾可以自定義修改,這裡就不展開了,如果專案比較簡單不需要,在paths.js裡刪去對應的部分即可。

再來看我們修改的entry,我們在config資料夾中的utils.js 新增了getEntry方法,並在entry處引用。

'use strict'
// 省略...
const paths = require('./paths')
const fs = require('fs')
// 省略...
exports.getEntry = () => {
  const entryPath = path.resolve(paths.projectPath, 'entry')
  const entryNames = fs
      .readdirSync(entryPath)
      .filter(n => /\.js$/g.test(n))
      .map(n => n.replace(/\.js$/g, ''))
  const entryMap = {}

  entryNames.forEach(
      name =>
      (entryMap[name] = [
          ...['babel-polyfill', path.resolve(entryPath, `${name}.js`)] // 這裡需要安裝一下babel-polyfill
      ])
  )
  return entryMap
}

複製程式碼

實際上就是對當前專案entry檔案中的js檔案進行遍歷,如果是單個就是單入口,多個就是多入口。

建立2個專案

快速配置 webpack 多入口腳手架

  • assets 靜態資源
  • config.js 代理配置、打包地址等配置
  • entry 入口資料夾

demo1是一個單入口專案,demo2是一個多入口專案,如果是多入口專案,需要在entry增加對應的js檔案,如上圖中的more.html和more.js,上面的getEntry其實找的就是index.js和more.js。 我們再看一下demo2中entry中的index.js和more.js

// index.js
import Vue from 'vue'
import App from '../App'

new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>'
})

複製程式碼
// more.js
import Vue from 'vue'
import App from '../More'

new Vue({
  el: '#more',
  components: { App },
  template: '<App/>'
})

複製程式碼

引入對應的元件就好,再看下config.js

const host = 'http://xxx.com/api' // 測試地址

module.exports = {
  dev: {
    // proxy代理配置
    proxyTable: {
      '/api': {
        target: host, // 源地址
        changeOrigin: true, // 改變源
        logLevel: 'debug',
        ws: true,
        pathRewrite: {
          '^/api': '' // 路徑重寫
        }
      }
    },
  },
  build: {
      // build輸出路徑
      assetsRoot: '',
      publichPath: ''
    }
    // 是否啟用postcss-pxtorem外掛 https://github.com/cuth/postcss-pxtorem
    // pxtorem: true
}
複製程式碼

這裡就是根據需要自行配置了,如果不需要完全可以不要這個檔案,重要的還是entry的入口檔案。

打包出口配置

入口改好了,我們再看出口,找到如下內容

// webpack.dev.conf.js
plugins: [
    new webpack.DefinePlugin({
      'process.env': require('../config/dev.env')
    }),
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
    new webpack.NoEmitOnErrorsPlugin(),
    // https://github.com/ampedandwired/html-webpack-plugin
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: 'index.html',
      inject: true
    }),
    // copy custom static assets
    new CopyWebpackPlugin([
      {
        from: path.resolve(__dirname, '../static'),
        to: config.dev.assetsSubDirectory,
        ignore: ['.*']
      }
    ])
  ]
複製程式碼
// webpack.prod.conf.js
new HtmlWebpackPlugin({
      filename: config.build.index,
      template: 'index.html',
      inject: true,
      minify: {
        removeComments: true,
        collapseWhitespace: true,
        removeAttributeQuotes: true
        // more options:
        // https://github.com/kangax/html-minifier#options-quick-reference
      },
      // necessary to consistently work with multiple chunks via CommonsChunkPlugin
      chunksSortMode: 'dependency'
    }),
// 省略
// copy custom static assets
    new CopyWebpackPlugin([
      {
        from: path.resolve(__dirname, '../static'),
        to: config.build.assetsSubDirectory,
        ignore: ['.*']
      }
    ])
複製程式碼

HtmlWebpackPlugin的作用是生成一個 HTML5 檔案,CopyWebpackPlugin的作用是將單個檔案或整個目錄複製到構建目錄。我們在utils.js中新建2個方法getHtmlWebpackPlugin和getCopyWebpackPlugin,對這兩個方法進行替換,讓他們支援多入口。改動後如下

// webpack.dev.conf.js
 plugins: [
    new webpack.DefinePlugin({
      'process.env': require('./dev.env')
    }),
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
    new webpack.NoEmitOnErrorsPlugin(),
    // https://github.com/ampedandwired/html-webpack-plugin
    // 改動
    ...utils.getHtmlWebpackPlugin(baseWebpackConfig),
    // copy custom static assets
    // 改動
    ...utils.getCopyWebpackPlugin()
  ]
複製程式碼
// webpack.prod.conf.js
    // 改動
     ...utils.getHtmlWebpackPlugin(baseWebpackConfig),
    // 省略
    // 改動
     ...utils.getCopyWebpackPlugin()
複製程式碼
// utils.js
exports.getHtmlWebpackPlugin = baseWebpackConfig => {
  const HtmlWebpackPluginList = []
  const entryNames = Object.keys(baseWebpackConfig.entry)
  entryNames.forEach(name => {
      HtmlWebpackPluginList.push(
          new HtmlWebpackPlugin(
              Object.assign({
                      filename: config.build.filename && process.env.NODE_ENV == 'production' ? config.build.filename : `${name}.html`,
                      template: config.build.template && process.env.NODE_ENV == 'production' ? path.resolve(
                          paths.projectPath, config.build.template) : path.resolve(
                          paths.projectPath,
                          `${name}.html`
                      ),
                      inject: true,
                      excludeChunks: entryNames.filter(n => n !== name)
                  },
                  process.env.NODE_ENV === 'production' ? {
                      minify: {
                          removeComments: true,
                          collapseWhitespace: true
                              // removeAttributeQuotes: true
                      },
                      chunksSortMode: 'dependency'
                  } : {}
              )
          )
      )
  })
  return HtmlWebpackPluginList
}

exports.getCopyWebpackPlugin = () => {
  const projectStaticPath = path.resolve(paths.projectPath, 'static')
  const assetsSubDirectory =
      process.env.NODE_ENV === 'production' ?
      config.build.assetsSubDirectory :
      config.dev.assetsSubDirectory
  const rootConfig = {
      from: paths.static,
      to: assetsSubDirectory,
      ignore: ['.*']
  }
  const projectConfig = {
      from: projectStaticPath,
      to: assetsSubDirectory,
      ignore: ['.*']
  }
  return [
      new CopyWebpackPlugin(
          fs.existsSync(projectStaticPath) ? [rootConfig, projectConfig] : [rootConfig]
      )
  ]
}

複製程式碼

修改index.js

我們找到config裡index.js,對其做一些修改,讓我們可以在專案裡的config.js中配置代理,打包目錄,讓模板更靈活。

// config/index.js  改造前
 dev: {
    // Paths
    assetsSubDirectory: 'static',
    assetsPublicPath: '/',
    proxyTable: {},
    // Various Dev Server settings
    host: 'localhost', // can be overwritten by process.env.HOST 
 },
 build: {
    // Template for index.html
    index: path.resolve(__dirname, '../dist/index.html'),

    // Paths
    assetsRoot: path.resolve(__dirname, '../dist'),
    assetsSubDirectory: 'static',
    assetsPublicPath: '/',
    // 省略
  }
複製程式碼
//config/index.js 改造後
const paths = require('./paths')
const resolve = relativePath => path.resolve(paths.projectPath, relativePath)
const _config = require(resolve('config.js')) // 子專案webpack配置
 dev: {
    // Paths
    assetsSubDirectory: 'static',
    assetsPublicPath: '/',
    proxyTable: _config.dev.proxyTable,
    // Various Dev Server settings
    host: '0.0.0.0', // can be overwritten by process.env.HOST 
 },
 build: {
    // Template for index.html
    index: path.resolve(__dirname, '../dist/index.html'),

    // Paths
    assetsRoot: _config.build.assetsRoot || path.resolve(__dirname, '../dist'),
    assetsSubDirectory: 'static',
    assetsPublicPath: _config.build.publichPath || './',
    // 省略
  }
複製程式碼

到這裡,我們的多入口配置就基本完成了,注意修改過的配置檔案裡一些引用需要加上,檢查下路徑是否正確。

既然我們的目的就是打造多入口模板,那麼以demo2為例,執行npm run dev 在如果服務是http://localhost:8080,多頁面入口在瀏覽器訪問時url就是http://localhost:8080/more.html。注意要帶.html哦。 執行npm run build 我們會發現dist資料夾裡有2個html,說明多入口打包成功

快速配置 webpack 多入口腳手架

到此我們的專案模板就配置完成了。以後多人開發、多入口活動都可以在這個專案下進行開發了,此腳手架是用的vue-cli 2,最新的是vue腳手架是cli3,安裝cli3會覆蓋cli2,如果繼續使用cli2的init 需要npm install -g @vue/cli-init 然後就可以用vue init webpack my-project了。由於我這裡的webpack的版本是4.8.3,可能一些配置檔案和舊版本略有變化,改造時還需仔細觀察。

此篇不涉及webpack優化,只提供一種配置思路。如果感覺文章寫的不夠清楚,或者想直接使用這個模板,我的git上有完整的腳手架 傳送門,如果遇到問題或者好的建議,歡迎提出。

相關文章