Webpack学习笔记

傻瓜版Webpack2.x版本示例项目
  • 下载webpack-demo
  • 项目目录

    ├── app
    │    ├── app.js
    │    ├── app.scss
    │    ├── config.json
    │    ├── demo.js
    │    ├── demo.tmpl.html
    │    ├── greeter.js
    │    ├── icon.png
    │    ├── index.temp.html
    │    ├── jquery-1.11.3.min.js
    │    ├── main.js
    │    ├── main.scss
    ├── package.json
    └── webpack.config.js
  • 运行
    • npm install webpack -g 安装webpack,运行webpack -h查看是否安装成功
    • npm install webpack-dev-server -g 安装webpack-dev-server,可以通过一个socket.io服务实时监听文件的变化并自动刷新页面
    • 安装第三方npm模块npm install
    • 在根目录下运行webpack-dev-server命令开启本地服务
    • 直接打开http://localhost:8080/测试
  • package.json

    {
      "name": "camera",
      "version": "1.0.0",
      "description": "",
      "main": "index.js",
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
        "build": "webpack --progress --colors"
      },
      "author": "vcxiaohan",
      "license": "ISC",
      "devDependencies": {
        "css-loader": "^0.26.4",
        "extract-text-webpack-plugin": "^2.1.0",
        "file-loader": "^0.10.1",
        "html-webpack-plugin": "^2.28.0",
        "json-loader": "^0.5.4",
        "node-sass": "^4.5.0",
        "sass-loader": "^6.0.3",
        "style-loader": "^0.13.2",
        "url-loader": "^0.5.8",
        "webpack": "^2.2.1"
      }
    }
  • webpack.config.js

    var webpack = require('webpack');
        var HtmlWebpackPlugin = require('html-webpack-plugin');
        var ExtractTextPlugin = require('extract-text-webpack-plugin'); // 必须配合html-webpack-plugin才能生效
    
        module.exports = {
            devtool: 'source-map',
            //配置生成Source Maps,以便报错时能定位到具体的行列
            entry: { // 入口文件配置项
                main: __dirname + "/app/main.js",
                // js文件标识,传入HtmlWebpackPlugin的chunks参数位置,以便生成多个页面时能引用不同的js文件
                demo: __dirname + "/app/demo.js",
                // 
            },
            output: { // 输出文件配置项
                path: __dirname + "/public",
                // 打包后的文件存放的地方
                filename: "[name].js" // 打包后输出文件的文件名
            },
            module: { // 在配置文件里添加JSON loader
                loaders: [{
                    test: /\.json$/,
                    loader: "json-loader" // webpack2.x 版本不能省略loader后缀
                }, {
                    test: /\.scss$/,
                    loader: ExtractTextPlugin.extract({ // extract-text-webpack-plugin2.x 版本写法
                        fallback: 'style-loader',
                        use: 'css-loader!sass-loader'
                    })
                }, {
                    test: /\.(png|jpg)$/,
                    loader: 'url-loader?limit=10&name=[hash].[ext]' // url-loader需要配合file-loader使用才能生效,否则读取大图片的时候会报错
                }, ]
            },
            plugins: [ // 插件配置项
            new HtmlWebpackPlugin({ // 生成多页面
                template: __dirname + "/app/index.tmpl.html",
                // 使用模板
                filename: 'index.html',
                // 输出的html文件名
                chunks: ['main', 'common'],
                // 引用的js文件标识,必须要引入CommonsChunkPlugin独立出来的common文件
            }), new HtmlWebpackPlugin({ // 生成多页面
                template: __dirname + "/app/demo.tmpl.html",
                filename: 'demo.html',
                chunks: ['demo'],
            }), new webpack.BannerPlugin("by vcxiaohan"),
            //new webpack.optimize.UglifyJsPlugin(),// 压缩js
            new ExtractTextPlugin("[name].css"), // 提取css样式为单独的文件
            new webpack.optimize.CommonsChunkPlugin({ // 把main文件标识的公共部分提取出来独立成common文件
                name: 'common',
                chunks: ['main']
            }), ],
            devServer: { // 本地服务配置项
                contentBase: "./public",
                // 自定义本地服务器基本目录
                inline: true,
                // 实时刷新
                proxy: { // 代理
                    '/': {
                        target: 'http://v4.faqrobot.net',
                        changeOrigin: true // 解决跨域代理
                    }
                }
            }
        }
更新于2018-8-10
webpack3.12.0版本示例项目
  • package.json
{
  "name": "vue",
  "version": "1.0.0",
  "description": "A Vue.js project",
  "author": "LAPTOP-ASHTD2FO\\Think <vcxiaohan@foxmail.com>",
  "private": true,
  "scripts": {
    "dll": "webpack --config webpack.dll.conf.js",
    "dev": "webpack-dev-server --config webpack.conf.js",
    "start": "npm run dev",
    "unit": "jest --config test/unit/jest.conf.js --coverage",
    "e2e": "node test/e2e/runner.js",
    "test": "npm run unit && npm run e2e",
    "lint": "eslint --ext .js,.vue src test/unit test/e2e/specs",
    "build": "webpack --config webpack.conf.js"
  },
  "dependencies": {
    "jquery": "^3.3.1",
    "vue": "^2.5.2",
    "vue-router": "^3.0.1"
  },
  "devDependencies": {
    "add-asset-html-webpack-plugin": "^2.1.3",
    "autoprefixer": "^7.1.2",
    "babel-core": "^6.22.1",
    "babel-eslint": "^8.2.1",
    "babel-helper-vue-jsx-merge-props": "^2.0.3",
    "babel-jest": "^21.0.2",
    "babel-loader": "^7.1.1",
    "babel-plugin-dynamic-import-node": "^1.2.0",
    "babel-plugin-syntax-jsx": "^6.18.0",
    "babel-plugin-transform-es2015-modules-commonjs": "^6.26.0",
    "babel-plugin-transform-runtime": "^6.22.0",
    "babel-plugin-transform-vue-jsx": "^3.5.0",
    "babel-preset-env": "^1.3.2",
    "babel-preset-stage-2": "^6.22.0",
    "babel-register": "^6.22.0",
    "chalk": "^2.0.1",
    "chromedriver": "^2.27.2",
    "clean-webpack-plugin": "^0.1.19",
    "copy-webpack-plugin": "^4.0.1",
    "cross-spawn": "^5.0.1",
    "css-hot-loader": "^1.4.1",
    "css-loader": "^0.28.0",
    "eslint": "^4.15.0",
    "eslint-config-standard": "^10.2.1",
    "eslint-friendly-formatter": "^3.0.0",
    "eslint-loader": "^1.7.1",
    "eslint-plugin-import": "^2.7.0",
    "eslint-plugin-node": "^5.2.0",
    "eslint-plugin-promise": "^3.4.0",
    "eslint-plugin-standard": "^3.0.1",
    "eslint-plugin-vue": "^4.0.0",
    "extract-text-webpack-plugin": "^3.0.0",
    "file-loader": "^1.1.4",
    "friendly-errors-webpack-plugin": "^1.6.1",
    "happypack": "^5.0.0",
    "html-webpack-plugin": "^2.30.1",
    "jest": "^22.0.4",
    "jest-serializer-vue": "^0.3.0",
    "nightwatch": "^0.9.12",
    "node-notifier": "^5.1.2",
    "node-sass": "^4.9.3",
    "optimize-css-assets-webpack-plugin": "^3.2.0",
    "ora": "^1.2.0",
    "portfinder": "^1.0.13",
    "postcss-import": "^11.0.0",
    "postcss-loader": "^2.0.8",
    "postcss-url": "^7.2.1",
    "purify-css": "^1.2.5",
    "purifycss-webpack": "^0.7.0",
    "rimraf": "^2.6.0",
    "sass-loader": "^7.1.0",
    "selenium-server": "^3.0.1",
    "semver": "^5.3.0",
    "shelljs": "^0.7.6",
    "style-loader": "^0.22.0",
    "uglifyjs-webpack-plugin": "^1.1.1",
    "url-loader": "^0.5.8",
    "vue-jest": "^1.0.2",
    "vue-loader": "^13.3.0",
    "vue-style-loader": "^3.0.1",
    "vue-template-compiler": "^2.5.2",
    "webpack": "^3.12.0",
    "webpack-bundle-analyzer": "^2.9.0",
    "webpack-dev-server": "^2.9.1",
    "webpack-merge": "^4.1.0"
  },
  "engines": {
    "node": ">= 6.0.0",
    "npm": ">= 3.0.0"
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not ie <= 8"
  ]
}
  • webpack.dll.conf.js
const path = require('path')
const webpack = require('webpack')
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
const pkg = require('./package.json')
const library = '[name]_lib'

module.exports = {
  entry: {
    vendors: Object.keys(pkg.dependencies),
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].dll.js',
    chunkFilename: '[name].chunk.js',
    // DllPlugin插件需要该配置参数,用来生成manifest的映射名称
    library
  },
  plugins: [
    // 该插件将第三方静态资源单独打包处理,避免我们在各种环境中重复打包永远不会变化的文件,优化效果非常明显(需要配合webpack内置插件DllReferencePlugin一起使用)
    new webpack.DllPlugin({
      // 打包后的文件路径
      path: path.resolve(__dirname, 'dist/[name]-manifest.json'),
      name: library
    }),
    // 压缩js文件,不要使用webpack内置的压缩插件,因为其版本低,缓存和多线程压缩配置项都不生效,这里使用独立的压缩插件还是有效的,同时该插件在设置babel的module为false时默认开启js tree shaking功能
    new UglifyJsPlugin({
      // 开启缓存
      cache: true,
      // 开启多线程并行处理
      parallel: true,
    }),
  ]
}
  • webpack.conf.js
const path = require('path')
const webpack = require('webpack')
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin')
const CleanWebpackPlugin = require('clean-webpack-plugin')
const glob = require('glob')
const PurifyCSSPlugin = require('purifycss-webpack')
const os = require('os')
const HappyPack = require('happypack')
const threadPool = HappyPack.ThreadPool({ size: os.cpus().length })

// 样式loader配置文件,因为有多处用到,故抽离成方法以便调用,一个ExtractTextPlugin实例是allInOne模式的,即所有的css合并成一个css文件,如果想抽离成多个文件,需要生成多个ExtractTextPlugin实例分别调用,如果想再优化的话,可以配合DllPlugin插件(这里我偷个懒,没有分开配置css和scss,请看我对css文件的正则,导致css和scss都会走这个方法,其实css是不需要sass-loader的)
function generateLoaders() {
  return ExtractTextPlugin.extract({
    fallback: 'style-loader',
    use: ['css-loader', 'sass-loader'],
  })
}

module.exports = {
  // source map配置项,由于打包后的bundle是一个文件,出现js错误后,不能定位到具体的模块文件,开启此项,帮我们解决此问题,但是不同的source map模式处理速度不同,建议不同环境,选择不同的模式
  // devtool: 'cheap-module-eval-source-map',
  entry: {
    app: './app.js',
    // app2: './app2.js',
    // app3: './app3.js',
    // vendors: ['vue', 'jquery'],
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name]-[hash:7].bundle.js',
    chunkFilename: '[name].chunk.js'
  },
  resolve: {
    extensions: ['.js', '.vue', '.json'],
    alias: {
      // vue别名,我们在使用npm安装vue的时候,会安装vue的各种版本,适用于不同的环境,由于在webpack2.x版本中我们都是使用ES6 Module规范,如import、export等关键字,所以我们需要配置能遵循ES6 Module规范的那个vue版本
      'vue$': 'vue/dist/vue.esm.js',
      // 文件夹前缀别名,在引用某个模块时,我们需要写出绝对路径,很麻烦,有了这个别名,我们可以把绝对路径的前缀使用@来代替
      '@': path.resolve(__dirname, './'),
    }
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        use: {
          loader: 'vue-loader',
          options: {
            loaders: {
              /* // 这里可以省略,因为vue文件中的js会自动根据.babelrc文件的配置去处理
              js: {
                loader: 'babel-loader',
                // options: '.babelrc文件内容拷贝至此亦可',
              }, */
              css: generateLoaders(),
              scss: generateLoaders(),
            }
          }
        },
      }, {
        test: /\.js$/,
        /* use: {
          // 将相应的id任务的loader交给happypack去处理,由于happypack可以开启多个线程并行处理,可以优化速度,个人测试,效果并不理想,慎用,网上也看到很多人说使用happypack没有效果,个人测试用例:对element-ui、jquery、vue、vue-router共4个模块大约900kb,使用babel编译,正常编译和使用happypack编译时间上并无什么差别(happypack显示开启了4个线程,偶尔时间反而还会更多)
          loader: 'happypack/loader?id=babel',
        }, */
        use: {
          loader: 'babel-loader',
          /* // 使用babel-loader插件时,如果我们想要编译es6为低版本浏览器能兼容的es5的话,我们需要为该loader配置一些参数,而且每个需要用到babel-loader插件的地方,都要在相应的地方写配置参数,比如我们要编译.vue文件中的<script>块的es6语法时,需要用到vue-loader,在vue-loader配置参数中我们依然要写一遍babel-loader的配置参数,显得很繁琐,因此我们把要写的配置参数统统提取出来放到.babelrc文件中,所有要用到babel的地方,在编译时都会自动去查询有没有这个文件存在,而且庆幸的是这个文件支持JSON5格式,即我们在这个文件中可以随心所欲的写单引号、加单行、多行注释、所有的键都可以不加双引号,就像写一个普通的js对象一样,而不是写一个格式要求严格的JSON文件
          options: '.babelrc文件内容拷贝至此亦可', */
        },
        exclude: '/node_modules'
      }, {// 开启模块热更新后,style-loader会自动帮我们处理css模块并实现模块局部更新,但是由于我们又使用了ExtractTextPlugin插件抽离css样式为单独的文件,所以这时css热更新会失效(该插件缺少处理热更新的api),当然我们可以不用ExtractTextPlugin插件,这样一来我们引入的样式文件都是以style标签的形式插入html中,很不友好,为此网上有人专门写了一个CSS Hot Loader插件,使我们在使用ExtractTextPlugin插件的同时又能实现css模块热更新功能(总结:开发模式只需要热更新,提高编译速度,推荐使用写法1,生产模式只需要提取css文件,推荐使用写法2)
        test: /\.s?css$/,
        /* // css loader写法1(此写法需主动把plugins配置插件处的ExtractTextPlugin的代码注释)
        // 最简单的处理css的loader写法,hot为true时,css热更新生效,但是不能抽离css为单独的样式文件
        use: [
          { loader: 'style-loader' },
          { loader: 'css-loader' },
        ], */
        // css loader写法2
        // hot为true时,css热更新失效,但是能抽离css为单独的样式文件
        use: generateLoaders(),
        /* // css loader写法3(个人测试结果,该css-hot-loader的css热更新效果并不理想,慎用)
        // hot为true时,css热更新生效,同时能抽离css为单独的样式文件
        use: ['css-hot-loader'].concat(generateLoaders()), */
      }, {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          // 当图片文件的大小比limit大时,会把文件交给file-loader去处理,反之,则自己处理并返回该文件的base64字符串,我个人认为base64的利大于弊,在这里我设为-1,表示任何时候都不使用base64(base64优点:减少http请求、字符串可以使用gzip;缺点:一般图片转为base64后,总字符串的体积会变大,不可读,并且显得冗余,还会增加CSSOM解析时间,我比较倾向的是使用cssSprites的方案来合并小图标)
          limit: -1,
          name: './dist/img/[name].[hash:7].[ext]',
        }
      },
    ]
  },
  plugins: [
    /* // 打包结果可视化分析,该插件会自动打开一个窗口,展示打包后的结果,我们可以根据最终打包的模块依赖做分析、优化工作
    new BundleAnalyzerPlugin(), */
    // 该插件将第三方静态资源单独打包处理,避免我们在各种环境中重复打包永远不会变化的文件,优化效果非常明显(需要配合webpack内置插件DllPlugin一起使用)
    new webpack.DllReferencePlugin({
      manifest: require('./dist/vendors-manifest.json')
    }),
    // 生成页面插件,可以自定义模板、页面title,写多个即表示生成多页面
    new HtmlWebpackPlugin({
      template: 'test.html',
    }),
    // 生成script标签引用指定路径,以实现自动添加静态资源到页面中,由于我们使用了DllReferencePlugin,所以我们需要手动把打包后的资源路径添加到页面中,而该插件帮我们自动完成
    new AddAssetHtmlPlugin({
      filepath: path.resolve(__dirname, 'dist/*.dll.js'),
      includeSourcemap: false,
      // 文件名带有随机hash值,防止缓存,不能设置hash值长度,真的难受
      hash: true,
    }),
    // 提取css样式为单独的文件
    new ExtractTextPlugin({
      // 多入口时,必需使用name或contenthash值来确定打包出的css文件名,这样提取出来的多个css文件就会以不同的名字命名,避免相互覆盖
      filename: '[name]-[contenthash:7].css',
      allChunks: true,
    }),
    /* // 清理无用css,即css tree shaking,比如dom树中压根没有这个节点,但是我们却写了这个节点的样式,这个插件会帮我们除掉没有用的css样式,个人测试,效果并不理想,慎用
    new PurifyCSSPlugin({
      // 个人测试,只使用bootstrap的分页样式(其他的模块也有问题),进行shaking后,css文件确实小了很多,但是展示的分页样式跟用shaking之前差了很多
      paths: glob.sync(path.resolve(__dirname, './test.html')),
    }), */
    // 提取公用代码,webpack的一个优化点,比如某个模块我们在不同的入口分别引入了1次,那么最终打包的多个bundle中都会有该模块的代码,增大了代码体积,使用此插件我们可以从多个bundle中提取公用的模块,减少代码体积,但是注意提取过程需要时间,所以建议开发环境下开启
    new webpack.optimize.CommonsChunkPlugin({
      // 提取出来的公用代码的名称
      name: 'common',
      // 配置几个入口都引入该模块时才去提取,该值比较重要,决定了最终提取的公用代码的体积和无用代码量(无用代码量:如果你设为1,那么只要某个模块被某个入口引入了1次,该模块就会被提取,但是其他的入口可能并不需要这个模块,那么就会成为无用代码)
      minChunks: 2,
    }),
    // 注入全局变量,相当于使用别名来代替每次手动引入某个模块,如以下,我们在文件中就不需要再手动书写'import $ from \'jquery\''了
    new webpack.ProvidePlugin({
      $: 'jquery',
    }),
    /* // 该插件让webpack能同时使用多个线程去处理loader,个人测试,效果并不理想,慎用
    new HappyPack({
      // 处理的任务id
      id: 'babel',
      loaders: [{
        loader: 'babel-loader',
        options: {
          presets: [
            ['babel-preset-env', {
              targets: {
                browsers: ['last 2 versions']
              }
            }]
          ]
        }
      }],
      // 使用的线程数
      threadPool
    }), */
    // 在打包前对指定文件夹清理,由于我们加了hash值来命名文件,每次文件改动后,会重新生成新的文件,所以需要定时清理文件夹,开发环境不需要使用
    new CleanWebpackPlugin(['dist'], {
      // 不写,也不会报错
      root: __dirname,
      // 清理时,排除某些文件,比如我们的第三方依赖库,从来没有改变过,所以不需要清理
      exclude: ['vendors.dll.js', 'vendors-manifest.json'],
    }),
    // hot为true时,必须使用该插件热更新才能生效
    new webpack.HotModuleReplacementPlugin(),
    // 热更新时,输出本次热更新的模块相对路径
    new webpack.NamedModulesPlugin(),
  ],
  devServer: {// 当使用webpack-dev-server模块来启动项目时,该配置项生效,会开启一个node服务器
    // 开启热更新
    hot: true,
    // 路由错误处理,当我们访问一个不存在的路由时,express会报404,该插件在出现此情况时,重定向到某个自己写的页面,更友好的提醒开发者
    historyApiFallback: {
      rewrites: [
        {
          from: /./,
          to: '/404.html'
        }
      ]
    }
  }
}
细节整理
  • extract-text-webpack-plugin需要配合html-webpack-plugin使用才能生效
  • url-loader需要配合file-loader使用才能生效,否则读取大图片的时候会报错(最新版的url-loader已经内置了file-loader)
  • CommonsChunkPlugin提取出来的文件,必须要在HtmlWebpackPlugin里面引用,否则报错webpackJsonp is not defined?
  • Babel其实是几个模块化的包,其核心功能位于称为babel-core的npm包中,不过webpack把它们整合在一起使用,但是对于每一个你需要的功能或拓展,你都需要安装单独的包(用得最多的是解析Es6的babel-preset-es2015包和解析JSX的babel-preset-react包)。
参考文档
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值