Webpack 学习

Webpack 入门

前言

有句话说的好:

懒是技术的第一推动力。

对于程序员来说,很多代码写过一次,就不想再写下一次,很多事做一次就不想重复去做,而且他们总有办法偷懒。
随着编写的代码变得越来越庞大和复杂,代码维护、打包、发布等流程也变得极为繁琐,这个时候,前端自动化工具就被创建出来了,Webpack 就是其中之一的自动化构建工具。

Webpack

一幅图来了解它:
image

概念

webpack 有四个核心概念:
* entry
* output
* loader
* plugins

entry 表示 webpack 以哪个模块来作为构建依赖关系图的起点,可以有一个或多个起点;
ouput 表示 webpack 构建之后输出文件,可以指定文件名和文件保存路径;
loader 表示 webpack 如何去处理依赖模块的源文件,如 js、css、ts、less 等文件;
plugins 用于扩展 webpack 功能的,他们在整个构建过程中生效,执行相关任务,具体在哪个wepack 生命周期需要看具体实现。

入门配置

一个基础的 webpack 配置文件(webpack.config.js):

var path = require('path')
var webpack = require('webpack')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
var UglifyPlugin = require('uglifyjs-webpack-plugin');
var HtmlPlugin = require('html-webpack-plugin');

var TARGET = process.env.npm_lifecycle_event
var APP_PATH = path.join(__dirname, '/src')
var isBuild = TARGET === 'build';

module.exports = function getConfig(){
    var config = {};

    config.entry = path.join(APP_PATH, 'index.js');

    config.output = {
        path: path.join(__dirname, '/dist'),
        filename: 'main.js'
    };

    config.module = {
        rules: [
            {
                test: /\.css$/,
                use: isBuild ? ExtractTextPlugin.extract({
                    fallback: 'style-loader',
                    use: [
                        'css-loader'
                    ]
                }) : ['style-loader', 'css-loader']
            }
        ]
    }

    config.plugins = [];

    if (isBuild) {
        config.plugins.push(
            new UglifyPlugin(),
            new ExtractTextPlugin('main.css')
        )
    } else {
        config.plugins.push(new HtmlPlugin());
    }


    config.devServer = { // webpack-dev-server
        contentBase: path.join(__dirname, '/dist'), // 本地服务器所加载的页面所在目录
        historyApiFallback: true, // 不跳转
        inline: true // 实时刷新
    }
    return config;
}();

起点(entry):是 src 目录下的 index.js 文件
输出(output):是在项目下 dist 目录生成一个 main.js 文件
加载器 (loader):对于 css 文件一定要使用 css-loader、style-loader,如果不使用的话项目生成是看不到样式文件的
插件(plugins):插件 plugins 是一个数组形式,如果我们想用一个插件,只需要require(“plugin”),然后 new plugin() 创建一个实例就行,他会在构建过程中处理任务
由于想要在 npm run dev 和 npm run build 时候是不同的情况,生产环境(build)的时候分离 css 代码和压缩 js 代码,开发的时候(dev)不做压缩和分离处理。所以对 loader 的处理规则做了判断,还有据构建环境(process.env.npm_lifecycle_event)引入不同的插件。

webpack-dev-server 是小型的 Node.js Express 服务器,在开发的时候使用能够方便调试代码。

当然,实际的项目的配置文件不可能这么简单,可能还有其他要求,比如多个 entry,公共库等情况处理,需要不断的去学习。

编写一个 loader

对于特定文件的处理,我们可以自己编写一个 loader 进行处理,webpack 允许我们这么做,查看 官方教程

一个例子:
有一个 index.js 文件,只有一行:

module.exports = 'hello world'

编写一个 define-loader 去获取数据
在项目根目录下创建一个 loader 目录,里面创建一个名为 define-loader.js 文件,内容如下:

module.exports = function (source) {
  // console.log(source) // 打印:module.exports = 'hello world'
  if (~this.request.indexOf('webpack/buildin/global.js')) {
    return source
  } else {
    return `
    global.define(function (module, exports) {
        ${source}
    })`
  }
} 

loader 只是一个导出为函数的 JavaScript 模块,这个函数接受的参数是源文件的字符串,返回经过处理后的文件。
上面我们使用了global对象,所以它会把 global.js 引入进去。

webpack.config.js 配置如下:

const path = require('path')

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.join(__dirname, 'dist'),
    filename: 'index.js'
  },
  module: {
    rules: [
      {
        test: /\.js/,
        use: [
          'define-loader'
        ]
      }
    ]
  },
  resolveLoader: {
    modules: [
      'node_modules',
      path.resolve(__dirname, 'loader')
    ]
  }
}

配置 resolveLoader.modules ,webpack 将会从 loader 目录中搜索 define-loader

测试(test.js)如下:

const assert = require('assert')

let mod = null
global.define = function (fn) {
  const module = { exports: {} }
  fn(module, module.exports)
  mod = module
}

describe('loader', function () {
  it('可以通过 define 拿到数据', function () {
    require('../dist/index')
    assert.equal(mod.exports, 'hello world')
  })
})

以上在 mac 系统上测试没有问题,但是在 windows 系统会报错,说 global 找不到。
使用如下配置可解决 windows 问题:

// 在webpack.config.js 中添加
target: 'node',
node: {
  global: true
}

更详细的 loader API 可以帮助写复杂的 loader

编写一个 plugin

webpack有很多内置插件比如 BannerPlugin,同时有很多第三方插件如上面基础配置使用到了压缩 js 代码的插件(uglifyjs-webpack-plugin),单独打包 css 文件的插件(extract-text-webpack-plugin)。如何编写,查看 官方教程

一个例子实现上面的 define-loader 功能:
我们的 plugin.js 实现:

const ConcatSource = require('webpack-sources').ConcatSource

class DefPlugin {
  constructor(name) {
    this.name = name;
  }

  apply(compiler) {
    compiler.plugin('compilation', (compilation) => { // Compilation creation completed
      compilation.templatesPlugin("render-with-entry", (source, chunk, hash) => {
        // console.log(source);

        if(this.name) {
          return new ConcatSource(`global.define([${JSON.stringify(this.name)}, 'module'], function(${this.name}, module) { `, source, "});");
        } else {
          return new ConcatSource(`global.define(['module'], function(module) { `, source, "});");
        }
      });
    });
  }
}

module.exports = DefPlugin

webpack.config.js 配置如下:

const path = require('path')
const Plugin = require('./plugin')

module.exports = {
    context: path.join(__dirname, 'src'),
    entry: './index.js',
    output: {
        libraryTarget: 'commonjs2', //表示生成文件使用 commonjs2 规范
        path: path.join(__dirname, 'dist'),
        filename: './bundle.js'
    },
    plugins: [
        // new Plugin
        new Plugin("exports")
    ]
}

测试(test.js)如下:

let mod = null
const assert = require('assert')

describe('plugin', function () {
  before(() => {
    global.define = function (deps, fn) {
      const module = { exports: {} }
      const args = deps.map(key => {
        if (key === 'require') return function () {}
        if (key === 'module') return module
        if (key === 'exports') return module.exports
      })
      fn.apply(null, args)
      mod = module
    }
  })

  it('可以不使用 Object.definePropery 中的 get 参数', function () {
    const a = require('../dist/bundle')
    assert.equal(mod.exports, 'hello world')
  })

  after(() => {
    global.define = null
    delete global.define
  })
})

由于 plugins 运行于 webpack 整个构建过程中,在 webpack 任何一个构建时期都可以去处理相应的任务,所以想要编写一个插件,必须了解 webpack 的生命周期(姑且这么称呼),需阅读源码
webpack 提供了两个很重要的对象 compilercompilation ,理解他们对于我们理解 webpack 的生命周期很有帮助
* compiler 对象代表了完整的 webpack 环境配置。这个对象在启动 webpack 时被一次性建立,当在 webpack 环境中应用一个插件时,插件将收到一个编译器对象的引用。 [Compiler API ]
* compilation 对象继承于 compiler,代表了一次单一的版本构建和生成资源。在这里,可以对资源的编译和生成的文件进行处理。 [Compilation API]

从上面插件代码中可以看到有 compiler.plugin 的使用,这是因为它继承了 Tapable 类,webpack 中很多对象继承了 Tapable 类,暴露了一个 plugin 的方法,这样可以注入自定义构建步骤,所以掌握 Tapable 插件类也是很重要的,Tapable 源码

总结

webpack 是一个可扩展的构建工具,我们除了可以使用内置的 plugins和 loader, 还可以自定义自行编写,有效的使用它能够提升实际项目的开发效率。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值