用webpack搭建多頁面項目

最基本的單入口配置

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

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

以上是單入口的基本配置,多頁面項目首先須要將入口文件變爲多入口,結構相似這樣css

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

那麼問題來了,如何將入口文件變成這種形式呢?這裏須要用到 nodefs模塊(fs模塊使用文檔),經過 fs.readdirSync 方法獲取文件名(fs使用以前須要require該模塊的),多層目錄須要遞歸獲取,具體操做方法以下:html

工具方法能夠單獨放在 webpack.uitl.jsvue

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;//對外吐出該方法,供其餘文件使用複製代碼

方法寫好後,多入口的基本配置就能夠這麼寫:java

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 對象中的鍵名。

node

loader配置

這裏列出經常使用的loader,根據使用的技術框架,可能會有些差異,個人項目用的是 react ,因此 babel-loader 會匹配jsx,若是使用其餘框架,則按需配置loader,例如使用vue,則須要新增長一個 vue-loader (具體請自行googlereact

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來幫助咱們完成該工做,直接上代碼,解釋寫在註釋中:jquery

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文件中,操做以下:webpack

// 公共代碼單獨打包
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中:git

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"
  }
}複製代碼
相關文章
相關標籤/搜索