Создайте многостраничный проект с помощью веб-пакета

внешний интерфейс Webpack

Самая базовая конфигурация с одним входом

Те, кто раньше использовал 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'
}

Так вот вопрос, как изменить файл входа в эту форму? Здесь нужно использоватьnodeизfsмодуль (документация по использованию модуля fs),пройти черезfs.readdirSyncспособ получить имя файла (Перед использованием fs вам необходимо потребовать модуль), многоуровневый каталог необходимо получать рекурсивно. Конкретный метод работы выглядит следующим образом:

Методы инструментов могут быть размещены индивидуально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Имя ключа в объекте.

конфигурация загрузчика

Здесь перечислены часто используемые загрузчики.В зависимости от используемой технической базы могут быть некоторые отличия.Мой проект используетreact,такbabel-loaderбудет соответствоватьjsx, если вы используете другие фреймворки, настройте загрузчик по мере необходимости, например с помощью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

  • только плагин html-пакета

    В большинстве случаев нам нужно не только упаковать файлы js, но и упаковать html-страницы, но html-файлы нельзя использовать в качестве входных файлов, а модуль fs можно использовать и для копирования html с билдом, но ссылочное отношение в файл проблемный, в это время мы можем использовать плагин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упакую все эти jsimportпередать их в свои 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 в голову в виде тегов стилей, от них зависит слишком много файлов, и упакованные файлы будут слишком большими.extract-text-webpack-pluginВы можете упаковать файл css в общедоступный файл css, а затем упаковать его в заголовок html в виде ссылки:

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По сути, при использовании веб-пакета для сборки его необходимо использовать на этапе отладки.Конкретные параметры указаны вофициальная документация по веб-пакетуОбъяснение более подробное, поэтому я не буду здесь больше говорить, просто вставьте код:

devServer: {
    contentBase: "./dist",//本地服务器所加载的页面所在的目录
    //historyApiFallback: true, //非hash模式路由不刷新(适用于单页面开发调试)
    noInfo:true,
    host:'192.168.102.103',
    port:'4001'
},


копия файла

Иногда нам нужно скопировать некоторые файлы непосредственно в каталог сборки, например, некоторые файлы конфигурации xml, черезfs.createReadStreamа такжеfs.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становится «развитием»

$ 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"
  }
}