用webpack搭建多頁面專案

寶爺發表於2017-11-17

最基本的單入口配置

之前使用webpack1.x的可以讀讀這篇文章《webpack升級指南和特性摘要》

module.exports={
  entry:'./src/index.js'  
  output:{
    path:__dirname+'/build',  //打包路徑
    publicPath:publicPath,   //靜態資源相對路徑
    filename:`[name].js` //打包後的檔名,[name]是指對應的入口檔案的名字
  }
}複製程式碼

以上是單入口的基本配置,多頁面專案首先需要將入口檔案變為多入口,結構類似這樣

{
    a:'./src/a.js',
    b:'./src/b.js'
}複製程式碼

那麼問題來了,如何將入口檔案變成這種形式呢?這裡需要用到 nodefs模組(fs模組使用文件),通過 fs.readdirSync 方法獲取檔名(fs使用之前需要require該模組的),多層目錄需要遞迴獲取,具體操作方法如下:

工具方法可以單獨放在 webpack.uitl.js

webpack.util.js

    //getAllFileArr方法
    //遞迴獲取檔案列表可能會在多處用到,所以可以封裝成方法
    //最後返回的資料格式如下
    /*
    [ [ 'a.js', './src/scripts/', './src/scripts/a.js' ],
        [ 'b.js', './src/scripts/', './src/scripts/b.js' ],
        [ 'c.js', './src/scripts/', './src/scripts/c.js' ] ]
    */
    function getAllFileArr(path){
        let AllFileList=[];
        getAllFile(path)
        function getAllFile(path) {
            var files = [];
            if( fs.existsSync(path) ) {   //是否存在此路徑
                files = fs.readdirSync(path); //獲取當前目錄下一層檔案列表
                files.forEach((file,index)=>{ //遍歷獲取檔案
                    var curPath = path + "/" + file;
                    if(fs.statSync(curPath).isDirectory()) { // recurse 檢視檔案是否是資料夾
                        getAllFile(curPath); //如果是資料夾,繼續遍歷獲取
                    } else {
                        if(file!=='.DS_Store'){
                            //.DS_Store是IDE配置檔案不用計入到檔案列表
                            AllFileList.push([file,path,curPath]) 
                        }
                    }
                });
            }
        };
        return AllFileList;
    }
    exports.getAllFileArr=getAllFileArr; //對外吐出該方法,供其他檔案使用複製程式碼
    //getEntry方法
    //最後返回如下資料結構
    /*
      {
          a: './src/scripts/a.js',
          b: './src/scripts/b.js',
          c: './src/scripts/c.js'
      }
    */
    function getEntyry(path){
        let file_list=getAllFileArr(path);
        let entry={};
          file_list.forEach((item)=>{
              entry[item[0].split('.').slice(0,-1).join('.')]=item[2] //鍵名去掉檔案字尾
          })
        return entry;
    }
    exports.getEntry = getEntry;//對外吐出該方法,供其他檔案使用複製程式碼

方法寫好後,多入口的基本配置就可以這麼寫:

webpac.config.js

const utils = require('./webpack.util.js')

module.exports={
  entry:utils.getEntyry('./src/script') //使用getentry方法獲取多入口
  output:{
    path:__dirname+'/build',  //打包路徑
    publicPath:publicPath,   //靜態資源相對路徑
    filename:`[name].js` //打包後的檔名,[name]是指對應的入口檔案的名字
  }
}複製程式碼

如上配置執行 webpack 命令後,會將入口檔案中所有的成員都打包到 build 下,檔名為 entry 物件中的鍵名。

loader配置

這裡列出常用的loader,根據使用的技術框架,可能會有些差別,我的專案用的是 react ,所以 babel-loader 會匹配jsx,如果使用其他框架,則按需配置loader,例如使用vue,則需要新增加一個 vue-loader (具體請自行google

  module:{
    rules:[
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
      },{
        test:/\.(css|scss)$/,
        loader:"style-loader!css-loader!postcss-loader!sass-loader" //webpack2.x是不支援loader簡寫的,這裡稍微注意一下
      },
      {
        test: /\.(png|jpg|gif|svg|eot|ttf|woff)$/,
        loader: 'file-loader',
        options: {
          name: 'source/[name].[ext]?[hash]' //該引數是打包後的檔名
        }
      }
    ]
  },複製程式碼


webpack plugins 配置

  • html單獨打包外掛

    多數情況下,我們不只是需要打包js檔案,而是需要將html頁打包,但是html檔案是不能作為入口檔案的,用fs模組也可以將html拷貝帶build,但是檔案裡的引用關係就麻煩了,這個時候我們就可以藉助一個外掛 html-webpack-plugin來幫助我們完成該工作,直接上程式碼,解釋寫在註釋中:

const htmlWebpackPlugin = require('html-webpack-plugin')

const utils = require('./webpack.util.js')
module.exports={
  entry:{
        //注意,webpack.optimize.CommonsChunkPlugin打包時候的chunk是跟entry的鍵名對應的
        app:'./src/app.js',
        lib:['src/lib/fastClick.js','src/lib/vConsole.js'] 
  } 
  output:{
    path:__dirname+'/build',  //打包路徑
    publicPath:publicPath,   //靜態資源相對路徑
    filename:`[name].js` //打包後的檔名,[name]是指對應的入口檔案的名字
  }
}


//遍歷所有需要打包的html檔案,分別配置打包
var html_list=utils.getAllFileArr('./src');
html_list.forEach((item)=>{
  var name = item[2];

  if(/\.html$/.test(item[0])){
    var prex='' //檔案字首,如果想給打包的html放在build下的html資料夾中,則var prex='html/'
    module.exports.plugins.push( //每個檔案分別配置外掛
      new htmlWebpackPlugin({ 
          favicon: './src/images/favicon.ico', //favicon路徑,通過webpack引入同時可以生成hash值
          filename: prex+item[0],
          template: name, //html模板路徑
          inject: true, //js插入的位置,true/'head'/'body'/false
          hash: true, //為靜態資源生成hash值
          chunks: [item[0].slice(0,-5),'common'],//需要引入的chunk,不配置就會引入所有頁面的資源
          minify: { //壓縮HTML檔案
              removeComments: true, //移除HTML中的註釋
              collapseWhitespace: false, //刪除空白符與換行符
              ignoreCustomFragments:[
              //     regexp  //不處理 正則匹配到的 內容
              ]
          },
          minify: false //不壓縮
      })
    )
  }
})複製程式碼
  • webpack.optimize.CommonsChunkPlugin外掛(webpack內建外掛文件

    專案中有許多js是多次被引用的,webpack 是會將這些js打包所有 import過它們的js中,這樣會導致打包後的js檔案都非常龐大,對此 webpack 內建了一個外掛 optimize.CommonsChunkPlugin,根據你的配置,會將多次被引用的檔案打包到一個公用的js檔案中,操作如下:

// 公共程式碼單獨打包
new webpack.optimize.CommonsChunkPlugin({
      name: 'common', //對外吐出的chuank名
      chunks:['app','lib'], //陣列,需要打包的檔案[a,b,c]對應入口檔案中的key
      minChunks:4, //chunks至少引用4次的時候打包
      filename: 'script/[name].js' //打包後的檔名
})

//以及一些其他的常用webpack內建外掛

//壓縮編譯後的程式碼,加了這個外掛後編譯速度會很慢,所以一般在生產環境加
new webpack.optimize.UglifyJsPlugin({
  sourceMap: true,
  compress: {
    warnings: false
  }
}),
/*
    UglifyJsPlugin 將不再支援讓 Loaders 最小化檔案的模式。debug 選項已經被移除。Loaders 不能從 webpack 的配置中讀取到他們的配置項。
    loader的最小化檔案模式將會在webpack 3或者後續版本中被徹底取消掉.

    為了相容部分舊式loader,你可以通過 LoaderOptionsPlugin 的配置項來提供這些功能。
*/
new webpack.LoaderOptionsPlugin({
  minimize: true
}),

//程式碼合併壓縮
new webpack.DefinePlugin({
  'process.env': {
    NODE_ENV: '"production"'
  }
})複製程式碼

預設情況下,webpack是將依賴的css以style標籤的形式插入到head中,檔案依賴多了,也會使打包後的檔案過大,extract-text-webpack-plugin 可以將css檔案打包成一個公共的css檔案,然後以link的方式打包到html的head中:

module:{
    rules:[
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
      },{
        test:/\.(css|scss)$/,
        //注意,使用ExtractTextPlugin時,css相關的loader配置需要修改成如下
        use: ExtractTextPlugin.extract({
          fallback: "style-loader",
          use: "css-loader!postcss-loader!sass-loader"
        })
      },
      {
        test: /\.(png|jpg|gif|svg|eot|ttf|woff)$/,
        loader: 'file-loader',
        options: {
          name: 'source/[name].[ext]?[hash]' //該引數是打包後的檔名
        }
      }
    ]
  },
new ExtractTextPlugin("[name].css?[hash]") //基礎配置只需要傳入打包名稱就行了複製程式碼


devserver

webpack-dev-server 基本上使用webpack構建時,除錯階段必用的,具體引數在 webpack官方文件 解釋的比較詳細,這裡就不多說了,簡單的貼一下程式碼:

devServer: {
    contentBase: "./dist",//本地伺服器所載入的頁面所在的目錄
    //historyApiFallback: true, //非hash模式路由不重新整理(適用於單頁面開發除錯)
    noInfo:true,
    host:'192.168.102.103',
    port:'4001'
},複製程式碼


檔案拷貝

有些時候,我們需要將某些檔案直接拷貝到build目錄下,如某些xml配置檔案,通過 fs.createReadStreamfs.createWriteStream 進行檔案的拷貝和移動(詳細說明請看 fs模組使用文件):

module.exports.plugins.push(function(){
    //打包完畢後將devconfig.xml檔案移動到build目錄下
    return this.plugin('done', function(stats) {
          // 建立讀取流
          var readable = fs.createReadStream( './devconfig.xml');
          // 建立寫入流
          var writable = fs.createWriteStream( './build/config.xml' );

          // 通過管道來傳輸流
          readable.pipe( writable );
    });
});複製程式碼


專案釋出

在開發的階段,我們往往不需要讓檔案打包到最優狀態,因為需要保證打包速度,但是在釋出的時候需要打包到最優狀態,這就需要我們對開發和生產兩種模式做不同的處理,我是採用 cross-env這個包獲取NODE_ENV的值來判斷當前是什麼環境:

if (process.env.NODE_ENV === 'production') {
  //生產模式下進行打包優化
}複製程式碼

如何來改變NODE_ENV的值呢? cross-env 可以幫助我們通過命令來修改, 執行以下命令,就能將 process.env.NODE_ENV的值變為'development'

$ cross-env NODE_ENV=development複製程式碼

暫時整理的就這麼多,後期有用到新的會繼續跟進,有錯誤的地方還忘指出,謝謝!! 最後貼出完整的配置:


完整配置

  • webpack.util.js

let fs =require('fs')

//獲取入口檔案物件
function getEntry(file_list){
  var entry={};
  file_list.forEach((item)=>{
      entry[item[0].split('.').slice(0,-1).join('.')]=item[2]
  })
  return entry;
  /*entry 看起來就是這樣
      {
          a: './src/scripts/a.js',
          b: './src/scripts/b.js',
          index: './src/scripts/index.js'
      }
  */
}
exports.getEntry = getEntry;


//遞迴遍歷所有檔案
function getAllFileArr(path){
    var AllFileList=[];
    getAllFile(path)
    function getAllFile(path) {
        var files = [];
        if( fs.existsSync(path) ) {   //是否存在此路徑
            files = fs.readdirSync(path);
            files.forEach(function(file,index){
                var curPath = path + "/" + file;
                if(fs.statSync(curPath).isDirectory()) { // recurse 檢視檔案是否是資料夾
                    getAllFile(curPath);
                } else {
                    if(file!=='.DS_Store'){
                        AllFileList.push([file,path,curPath])
                    }
                }
            });
        }
    };
    /*
        最後AllFileList 看起來就是這樣
        [ [ 'a.js', './src/scripts/', './src/scripts/a.js' ],
          [ 'b.js', './src/scripts/', './src/scripts/b.js' ],
          [ 'index.js', './src/scripts/', './src/scripts/index.js' ] ]
     */
    return AllFileList;
}
exports.getAllFileArr=getAllFileArr;


//刪除資料夾 ,遞迴刪除
function deleteFolderRecursive(path) {
    var files = [];
    if( fs.existsSync(path) ) {
        files = fs.readdirSync(path);
        files.forEach(function(file,index){
            var curPath = path + "/" + file;
            if(fs.statSync(curPath).isDirectory()) { // recurse 檢視檔案是否是資料夾
                deleteFolderRecursive(curPath);
            } else { // delete file
                fs.unlinkSync(curPath);
            }
        });
        fs.rmdirSync(path);
    }
};

exports.deleteFolderRecursive=deleteFolderRecursive;複製程式碼


  • webpack.config.js

const path =require('path');
const fs =require('fs')
const webpack = require('webpack');
const htmlWebpackPlugin = require('html-webpack-plugin')
let ExtractTextPlugin = require('extract-text-webpack-plugin')

const utils = require('./webpack.util.js')

//打包之前刪除build資料夾
utils.deleteFolderRecursive('./build')

let publicPath='./'
    ,updateTime=new Date().getTime()

module.exports={
  entry:{
    ...utils.getEntry(utils.getAllFileArr('./src/script')),
    react:'react',
    jquery:'jquery'
  },
  output:{
    path:__dirname+'/build',
    publicPath:publicPath,
    filename:`script/[name].js`
  },
  module:{
    rules:[
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
      },{
        test:/\.(css|scss)$/,
        // loader:"style-loader!css-loader!postcss-loader!sass-loader"
        use: ExtractTextPlugin.extract({
          fallback: "style-loader",
          use: "css-loader!postcss-loader!sass-loader"
        })
      },
      {
        test: /\.(png|jpg|gif|svg|eot|ttf|woff)$/,
        loader: 'file-loader',
        options: {
          name: 'source/[name].[ext]?[hash]'
        }
      }
    ]
  },
  resolve:{
    extensions:['.scss', '.js','.jsx'],
    alias: {
      'bassCss':__dirname+'/src/css',
      'image':__dirname+'/src/image',
      'components':__dirname+'/src/script/components'
    }
  },
  devServer: {
    // contentBase: "./dist",//本地伺服器所載入的頁面所在的目錄
    // historyApiFallback: true, //不跳轉
    noInfo:true,
    host:'192.168.102.103',
    port:'4001'
  },
  plugins:[
    new webpack.optimize.UglifyJsPlugin({
      sourceMap: true,
      compress: {
        warnings: false
      }
    }),
    new webpack.LoaderOptionsPlugin({
      minimize: true
    }),
    // 公共程式碼單獨打包
    new webpack.optimize.CommonsChunkPlugin({
      name: 'common', //對外吐出的chuank名
      chunks:Object.keys(utils.getEntry(utils.getAllFileArr('./src/script'))), //陣列,需要打包的檔案[a,b,c]對應入口檔案中的key
      minChunks:4, //chunks至少引用4次的時候打包
      filename: 'script/[name].js' //打包後的檔名
    })
  ]
}

module.exports.plugins.push(new ExtractTextPlugin("[name].css?[hash]"))

//複製 config.xml 到 build目錄下
module.exports.plugins.push(function(){

    return this.plugin('done', function(stats) {
          // 建立讀取流
          var readable = fs.createReadStream( './devconfig.xml');
          // 建立寫入流
          var writable = fs.createWriteStream( './build/config.xml' );

          // 通過管道來傳輸流
          readable.pipe( writable );
    });
});


//將html檔案打包
var html_list=utils.getAllFileArr('./src');
html_list.forEach((item)=>{
  var name = item[2];

  if(/\.html$/.test(item[0])){
    var prex=''//item[1].indexOf('html')>-1?'html/':''
    module.exports.plugins.push(
      new htmlWebpackPlugin({ //根據模板插入css/js等生成最終HTML
          // favicon: './src/images/favicon.ico', //favicon路徑,通過webpack引入同時可以生成hash值
          filename: prex+item[0],
          template: name, //html模板路徑
          inject: true, //js插入的位置,true/'head'/'body'/false
          hash: true, //為靜態資源生成hash值
          chunks: [item[0].slice(0,-5),'common'],//需要引入的chunk,不配置就會引入所有頁面的資源
          minify: { //壓縮HTML檔案
              removeComments: true, //移除HTML中的註釋
              collapseWhitespace: false, //刪除空白符與換行符
              // ignoreCustomFragments:[
              //     /\{\{[\s\S]*?\}\}/g  //不處理 {{}} 裡面的 內容
              // ]
          },
          minify: false //不壓縮
      })
    )
  }
})




//生產模式打包的時候進行程式碼壓縮合並優化
if (process.env.NODE_ENV === 'production') {
  module.exports.devtool = '#eval-source-map'
  module.exports.output.publicPath='./'

  //釋出時給檔名加上時間
  module.exports.plugins[module.exports.plugins.length-1]=new ExtractTextPlugin(`css/${updateTime}_[name].css?[hash]`);
  module.exports.output.filename=`script/${updateTime}_[name].js`;

  module.exports.plugins = (module.exports.plugins || []).concat([
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: '"production"'
      }
    })
  ])
}複製程式碼


  • package.json

{
  "name": "yit01",
  "version": "1.0.0",
  "description": "",
  "main": "webpack.config.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "cross-env NODE_ENV=development webpack --progress --hide-modules --watch",
    "build": "cross-env NODE_ENV=production webpack --progress --hide-modules"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "babel-core": "^6.26.0",
    "babel-loader": "^7.1.2",
    "babel-preset-es2015": "^6.24.1",
    "babel-preset-react": "^6.24.1",
    "babel-preset-stage-2": "^6.24.1",
    "cross-env": "^5.1.1",
    "css-loader": "^0.28.7",
    "extract-text-webpack-plugin": "^3.0.2",
    "file-loader": "^1.1.5",
    "html-webpack-plugin": "^2.30.1",
    "node-sass": "^4.6.0",
    "postcss-loader": "^2.0.8",
    "sass-loader": "^6.0.6",
    "scss-loader": "0.0.1",
    "style-loader": "^0.19.0",
    "webpack": "^3.8.1",
    "webpack-dev-server": "^2.9.4"
  },
  "dependencies": {
    "jquery": "^3.2.1",
    "js-md5": "^0.7.2",
    "react": "^16.0.0",
    "react-dom": "^16.0.0",
    "react-fastclick": "^3.0.2",
    "react-lazyload": "^2.3.0",
    "react-redux": "^5.0.6",
    "react-router": "^4.2.0",
    "react-router-dom": "^4.2.2",
    "redux": "^3.7.2"
  }
}複製程式碼

相關文章