程序员互联网精选

webpack学习入门

2018-08-22  本文已影响9人  IT干货

内容提纲

electron-vue项目中的webpack工程实例

从electron-vue项目中的实际使用例子来入手

如下是 webpack.renderer.config.js 文件

'use strict'

process.env.BABEL_ENV = 'renderer'

const path = require('path')
const { dependencies } = require('../package.json')
const webpack = require('webpack')

const BabiliWebpackPlugin = require('babili-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')

let whiteListedModules = ['vue']

let rendererConfig = {
  /**
   * 控制打包后代码的形式,影响调试。当前设置为cheap-module-eval-source-map,调试时仅可看到原始源代码      
   *(仅限行),如果同时使用了代码压缩插件,代码被合成一行,将无法获取有效的调试内容。
   */
  devtool: '#cheap-module-eval-source-map',
  
  /**
   * webpack处理项目的入口文件,以该文件作为构建其内部依赖图的开始。
   * 通常是项目的 index 或 main 文件
   * 注意 entry 可以是一个数组,即 webpack 可以处理多个入口文件,将其
   */
  entry: {
    renderer: path.join(__dirname, '../src/renderer/main.js')
  },
  
  /**
   * 声明外部依赖,在此声明的文件,即使在工程中使用import引入,也不会被打到bundle中。
   * 一般我们会把package.json中的dependencies排除在外。
   * 当我们需要使用cdn引用第三方库时,我们也要将其放在external中,比如使用jquery cdn
   * 思考1:node_modules中的三方库被排除在打包的bundle文件外,为什么我们的程序在打包后仍然能够使用
   * 三方库
   */
  externals: [
    ...Object.keys(dependencies || {}).filter(d => !whiteListedModules.includes(d))
  ],
    
  /**
   * 模块是 webpack 的核心,webpack 帮我们把文件打包成模块,然后我们就可以使用 import 的方式来
   * 使用。webpack 通过 loader 来各种处理文件,因此我们可以在项目中使用模块化的方法引用任何类型的文件
   */
  module: {
    rules: [
      {
        /**
         * test 做正则匹配,所有符合该匹配规则的文件都将应用该 loader 规则
         */ 
        test: /\.(js|vue)$/,
        /**
         * enfore 用来定义规则的执行顺序,取值 ['pre', 'post'],此处为 pre,将在所有 loader 执行前
         * 执行。即在所有 loader 规则之前,使用 eslint-loader 对代码做静态检查
         */ 
        enforce: 'pre',
        /**
         * exclude 排除不想应用该规则的目录
         */ 
        exclude: /node_modules/,
        /**
         * useEntry 配置loader
         */ 
        use: {
          loader: 'eslint-loader',
          options: {
            formatter: require('eslint-friendly-formatter')
          }
        }
      },
      {
        test: /\.css$/,
        /**
         * ExtractTextPlugin 插件,用于将 *.css 分离到单独的 style.css 文件中,而不是作为样式
         * 放入 bundle.js 文件中
         * fallback 是当 CSS 没有被提取时应用的 loader
         * style-loader 的作用是使用 <style></style> 标签将样式添加到页面 dom 中
         */ 
        use: ExtractTextPlugin.extract({
          fallback: 'style-loader',
          use: 'css-loader'
        })
      },
      {
        test: /\.scss$/,
        /**
         * 不同于 use 的形式,loader 写成内联的方式
         * 执行顺序是“从右到左,从下到上“,即 sass-loader -> css-loader -> vue-style-loader
         * loader 之间通过 ! 分隔,如果需要为 loader 配置参数,则可以使用 ?key=value&foo=bar 的形
         * 式,类似于 url 的参数。
         */
        loader: 'vue-style-loader!css-loader!sass-loader'
      },
      {
        test: /\.html$/,
        use: 'vue-html-loader'
      },
      {
        test: /\.js$/,
        use: 'babel-loader',
        exclude: /node_modules/
      },
      {
        test: /\.json$/,
        use: 'json-loader'
      },
      {
        test: /\.node$/,
        use: 'node-loader'
      },
      {
        test: /\.vue$/,
        use: {
          loader: 'vue-loader',
          options: {
            extractCSS: process.env.NODE_ENV === 'production',
            loaders: {
              /**
               * 带参数的 loader
               */
              sass: 'vue-style-loader!css-loader!sass-loader?indentedSyntax=1',
              scss: 'vue-style-loader!css-loader!sass-loader'
            }
          }
        }
      },
      {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        use: {
          loader: 'url-loader',
          /**
           * query 老的写法,等同于 options
           */
          query: {
            limit: 10000,
            name: 'imgs/[name].[ext]'
          }
        }
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        use: {
          loader: 'url-loader',
          query: {
            limit: 10000,
            name: 'fonts/[name].[ext]'
          }
        }
      },
      {
        test: /\.(ogg)$/,
        use: 'file-loader'
      }
    ]
  },
  plugins: [
    new ExtractTextPlugin('styles.css'),
      
    /**
     * HtmlWebpackPlugin 这个插件的作用是依据一个简单的index.html模板,生成一个自动引用你打包后的JS文
     * 件的新index.html。这在每次生成的js文件名称不同时非常有用(比如添加了hash值)
     * 当我们发布一个网站应用时,我们需要通过生成带 hash 值的文件名来更新浏览器对静态文件的缓存
     * 这样会有一个问题,我们每次使用 webpack 打包后都需要手动修改 index.html 文件中引用的 
     * bundle.[hash].js 文件名,显然很麻烦。
     * HtmlWebpackPlugin 的引入就是为了解决此问题,通过配置一个 index 模板,webpack 会在每次打包完成
     * 后,将 bundle.[hash].js 的引用注入 index.html 中
     */
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: path.resolve(__dirname, '../src/index.ejs'),
      minify: {
        collapseWhitespace: true,
        removeAttributeQuotes: true,
        removeComments: true
      },
      nodeModules: process.env.NODE_ENV !== 'production'
        ? path.resolve(__dirname, '../node_modules')
        : false
    }),
      
    /**
     * HotModuleReplacementPlugin webpack 提供的最有用的功能之一。它允许在运行时更新各种模块,而无需
     * 进行页面刷新。使用 HMR 的方法:
     * 1.在webpack配置文件中添加HMR插件;
     * 2.在Webpack Dev Server中添加“hot”参数; 在我们的electron-vue应用中使用了接口调用的方式,查看
     * dev-runner.js 文件
     */
    new webpack.HotModuleReplacementPlugin(),
      
    new webpack.NoEmitOnErrorsPlugin(),
      
    /**
     * 自动加载模块,并定义到全局变量,随时可以引用。相当于 import 了一个全局对象
     * new webpack.ProvidePlugin({
     *     identifier: 'module1',
     *     identifier: ['module1', 'property1'],
     *     // ...
     * });
     * 任何时候,当 identifier 被当作未赋值的变量时,module 就会自动被加载,并且 identifier 会被这个
     * module 输出的内容所赋值。
     * 支持支持命名导出,如我们想全局使用 _.map 这个方法,那么我们可以这样来引入:
     * new webpack.ProvidePlugin({
     *     _map: ['lodash', 'map']
     * });
     * 在项目中即可以使用 _map 来使用 _.map 方法
     */
    new webpack.ProvidePlugin({
      jQuery: "jquery",
      jquery: "jquery",
      $: "jquery",
      "window.jQuery": "jquery"
    })
  ],
    
  /**
   * 定义打包文件名,[name] 参数由 entry 中的key决定,此处为render.js
   * 这里文件名没有使用 hash 值,是因为在 electron 项目中,升级不需要考虑浏览器缓存的问题,整个应用都会
   * 被替换
   */
  output: {
    filename: '[name].js',
    libraryTarget: 'commonjs2',
    path: path.join(__dirname, '../dist/electron')
  },
    
  /**
   * 在此可以定义 import 或 require 的别名,来确保模块引入变得更简单。
   * 当目录下的文件没有文件后缀时,使用 extensions 中的后缀依次尝试解析
   */
  resolve: {
    alias: {
      '@': path.join(__dirname, '../src/renderer'),
      'vue$': 'vue/dist/vue.esm.js',
      'config' : path.join(__dirname, '../config'),
      'components': path.join(__dirname, '../src/renderer/components'),
      'utils': path.join(__dirname, '../src/renderer/utils'),
      'renderer': path.join(__dirname, '../src/renderer'),
      'services': path.join(__dirname, '../src/renderer/services'),
      'store': path.join(__dirname, '../src/renderer/store'),
      'router': path.join(__dirname, '../src/renderer/router'),
      'plugins': path.join(__dirname, '../src/renderer/plugins'),
      'css': path.join(__dirname, '../src/renderer/css'),
      'images': path.join(__dirname, '../src/renderer/images'),
      'jquery': path.join(__dirname, '../node_modules/jquery/src/jquery')
    },
    extensions: ['.js', '.vue', '.json', '.css', '.node']
  },
  /**
   * 构建目标, https://webpack.docschina.org/configuration/target
   */
  target: 'electron-renderer'
}

/**
 * Adjust rendererConfig for development settings
 */
if (process.env.NODE_ENV !== 'production') {
  /**
   * DefinePlugin 允许创建一个在编译时可以配置的全局常量。
   * 比如定义后端接口的url
   * https://webpack.docschina.org/plugins/define-plugin/
   */
  rendererConfig.plugins.push(
    new webpack.DefinePlugin({
      '__static': `"${path.join(__dirname, '../static').replace(/\\/g, '\\\\')}"`
    })
  )
}

/**
 * Adjust rendererConfig for production settings
 */
if (process.env.NODE_ENV === 'production') {
  rendererConfig.devtool = ''

  rendererConfig.plugins.push(
    /**
     * 这是一款基于 Babel 的压缩工具,支持 es6 的一些特性,取代 UglifyJS
     */
    new BabiliWebpackPlugin({
      removeConsole: true,
      removeDebugger: true
    }),
    /**
     * Copies individual files or entire directories to the build directory.
     * 一般会使用插件把需要使用的静态文件考到构建后对应的目录
     */
    new CopyWebpackPlugin([
      {
        from: path.join(__dirname, '../static'),
        to: path.join(__dirname, '../dist/electron/static'),
        ignore: ['.*']
      }
    ]),
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': '"production"'
    }),
    /**
     * 用于 webpack2 对 webpack1 配置的兼容,抹平差异
     */
    new webpack.LoaderOptionsPlugin({
      minimize: true
    })
  )
}

module.exports = rendererConfig

思考1:external

node_modules中的三方库被排除在打包的bundle文件外,为什么我们的程序在打包后仍然能够使用三方库

// package.json
{
    ...
    "build": {
        ...
        "files": [
          "dist/electron",
          "node_modules/",
          "package.json"
        ]
    }
}

在 package.json 文件中,配置了 files 文件,electron-build 在打包时会将 files 中的文件夹全部打包到工程中,即 resources/app.asar 文件

我们使用命令查看

asar list ./resources/app.asar

可以看到如下一系列文件

/node_modules/argparse/lib/action/store/false.js
/node_modules/argparse/lib/action/store/true.js
/node_modules/argparse/lib/action/append
/node_modules/argparse/lib/action/append/constant.js
...
/dist
/dist/electron
/dist/electron/index.html
/dist/electron/main.js
/dist/electron/renderer.js
/dist/electron/static/update/process.html
/dist/electron/static/update/process.js
/dist/electron/static/update/libs
...
/dist/electron/fonts
/dist/electron/fonts/element-icons.ttf

webpack与gulp/grunt

Gulp/Grunt是一种能够优化前端的开发流程的工具,而WebPack是一种模块化的解决方案,不过Webpack的优点使得Webpack在很多场景下可以替代Gulp/Grunt类的工具

Grunt和Gulp的工作方式

在一个配置文件中,指明对某些文件进行类似编译,组合,压缩等任务的具体步骤,工具之后可以自动替你完成这些任务。

Webpack的工作方式是

把你的项目当做一个整体,通过一个给定的主文件(如:index.js),Webpack将从这个文件开始找到你的项目的所有依赖文件,使用loaders处理它们,最后打包为一个(或多个)浏览器可识别的JavaScript文件。

如果实在要把二者进行比较,Webpack的处理速度更快更直接,能打包更多不同类型的文件。

开发模式热更新

开发模式热更新分为两部分:

通常使用webpack-dev-server

注意

依靠HMR的热更新机制,我们可以享受修改的文件被实时同步到浏览器的便利,但对于页面来说,这并不是全部。我们仍然需要在页面的js代码中做一些处理。

比如:我们在按钮上绑定了一个click事件的处理函数handleClick,我们修改了handleClick的代码,它被推送到浏览器中,但是页面并不会自动替换之前绑定的click事件,当我们点击按钮时,仍然得到旧的响应。

所以除了使用webpack-dev-server,我们需要在js代码中监听HMR 的 accept 方法,在此方法中更新类似的处理函数。

这也就是为什么在react项目中,我们需要使用react-hot-loader库,本质上是实现了HMR 的 accept 的处理。

参考文档

入门 Webpack,看这篇就够了

官方中文文档

Webpack Hot Module Replacement 的原理解析

上一篇下一篇

猜你喜欢

热点阅读