webpack v3 学习笔记(一) 打包js

265 阅读2分钟

webpack

随着前端技术的发展,前后端开发的分离,前端项目的构建显得越来越有必要,而 Webpack 就是一个前端的打包工具,帮助我们完成前端项目的构建。

前端的项目构建工具也逐渐的由借助其他语言的构建工具,到使用 Grunt,后来被 Stream 概念的 Gulp 所取代,再到现在的 Rullup, Webpack。而 Webpack 也成为了三大框架官方脚手架所使用的构建工具,在我们做项目的时候会经常接触到,非常有学习了解的必要。

其起源于作者在 Google 就职时,后项目被 Instagram 支持。

其各个版本更迭情况大概如下:

  1. v1
    • 编译,打包
    • 模块热更新
    • 代码分割
    • 文件处理
  2. v2
    • Tree Sharking
    • 支持 ES Module
    • 动态 Import()
  3. v3
    • Scope Hoisting
    • Magic Comments
  4. v4
    • 零配置
    • mode
    • 新增支持的 js 模块
    • 插件的改变和优化

Webpack 的作用,用官网的一张图就能概括:

核心概念

v3 中有四个核心概念:entry,output,loader,plugin

entry

entry 是 webpack 进行打包的入口,webpack 会从入口开始去寻找入口文件的依赖项进行打包。

output

output 是 webpack 打包后的输出文件。

loader

webpack 本身只能打包 js,而其他类型的文件就要借助于各种各样的 loader。

plugin

插件可以帮助我们实现更多样的功能。

打包普通 js

npm init
npm install webpack@^3.0.0
touch webpack.config.js
mkdir src
touch src/app.js
touch src/sum.js

app.js:

import sum from './sum'

console.log(sum(1 + 2))

sum.js:

export default function (a, b) {
  return a + b
}

webpack.config.js

module.exports = {
  entry: {
    app: './src/app.js'
  },
  output: {
   // output 中可以使用占位符
    filename: '[name].[hash].js',
    path: __dirname + '/dist'
  }
}

运行 webpack 打包命令即可成功打包。

打包 ES6

npm init
npm install webpack
// babel包会在下面解释作用
npm install babel-loader@8.0.0-beta.0 @babel/core @babel/preset-env @babel/polyfill @babel/runtime-corejs3 @babel/plugin-transform-runtime core-js regenerator-runtime
touch webpack.config.js
touch .babelrc
mkdir src
touch src/app.js
touch src/sum.js

app.js:

import sum from './sum'

let a = 1
const b = 3
const c = [...[1, 2], 3]

console.log(sum(a + b + c[0]))

sum.js:

export default (a, b, c) => a + b + c

为了让 webpack 识别并打包 ES6 语法,我们就需要使用 babel 和 babel-loader 了。

如果要使用 babel v7 版本,则 babel-loader 需要安装 babel-loader@8.0.0-beta.0

webpcak.config.js

module.exports = {
  entry: './src/app.js',
  output: {
    filename: '[name].[hash].js',
    path: __dirname + '/dist'
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        use: 'babel-loader',
        exclude: '/node_modules/'
      }
    ]
  }
}

.babelrc

{
  "presets": ["@babel/preset-env"]
}

我们在 rules 里配置了 .js 结尾的文件调用 babel-loader。使用了 @babel-core(核心) 和 配置了 @babel/preset-env (识别ES6语法),

执行 webpack 命令打包,ES6 语法正确转译:


@babel/preset-env 只是让babel 支持了新的 ES6 语法,但是 ES6 中新加的功能在一些低级的浏览器中并不会被支持,这就需要使用到我们安装的其他的 babel 包了。下面说到的包其实都是必须要安装到 --save 中的, 因为这些包都是要在应用中使用到的(@babel/plugin-transform-runtime 为 dev,搭配 @babel/runtime 使用)

其主要分为两类使用:

@babel/polyfill

@babel/polyfill 其是就是一个功能包,里面帮我们实现了 ES6 的各种 API,我们只需要在入口文件中引入即可:

import 'babel-polyfill'

注意这样我们的打包生成文件会大上许多,多出来的就是我们引入的全部的 polyfill 的大小,另外,引入的 polyfill 是会污染全局变量的。

使用core-js v2 的时候可以像上面引入。

core-js 到了 v3,@babel/polyfill 被废弃了,改而要使用 core-js regenerator-runtime,之前的全部引入 polyfill 也要改成:

import 'core-js/stable';
import 'regenerator-runtime/runtime';

在 babel 中,还可以通过 preset-env 中的配置来实现 polyfill 的按需引入来节省空间:

.babelrc:

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "useBuiltIns": "usage",
        // 指定core-js为v3
        "corejs": 3
      }
    ]
  ]
}

@babel/runtime & @babel/plugin-transform-runtime

借助于 @babel/runtime & @babel/plugin-transform-runtime 同样可以实现支持 ES6 API,并且这种方式是按需引入的,还不会污染全局变量。

之前的说法是这种方式比较适合开发库使用,但是这种方式是有缺陷的,如 [1,2,3].includes(1) 这种依赖于 Array.prototype.includes 的方法就不支持。所以在产品中还是使用 polyfill 的方式为好。

但是在新版本中可以指定 core-js 为 v3(对应的 runtime 包为 @babel/runtime-corejs3),就支持了,所以目前来看还是 runtime 的方法最优。

使用时也只要在 .babelrc 中配置:

{
  "presets": [
    "@babel/preset-env"
  ],
  "plugins": [
    ["@babel/plugin-transform-runtime", {
      "corejs": {
        "version": 3,
        "proposals": true
      }
    }]
  ]
}

打包 typescript

打包 ts 我们要借助 ts-loader 和 tsconfig.json(ts 编译设置文件)。

但是随着babel的发展,其实是更推荐使用 babel 的方式去转译 ts。至于为什么,我想把答案交给下面这篇译文:juejin.cn/post/684490…

ts-loader

npm init
//  最新的ts-loader要求webpackv4
npm install webpack@^4.0.0 webpack-cli typescript ts-loader
touch tsconfig.json
touch webpack.config.js
mkdir src
touch src/app.ts

app.ts:

const a = 45

interface Cat {
    name: String,
    sex: String
}

function touchCat(cat: Cat): void {
    console.log('miao, I am ' + cat.name)
}

touchCat({
    name: 'kafe',
    sex: 'male'
})

webpack.config.js:

module.exports = {
  mode: 'development',
  entry: './src/app.ts',
  output: {
    filename: '[name].[hash].js',
    path: __dirname + '/dist'
  },
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: 'ts-loader'
      }
    ]
  }
}

tsconfig.json:

{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es5",
    "allowJs": true
  },
  "include": [
    "./src/*"
  ],
  "exclude": [
    "./node_modules"
  ]
}

@babel/preset-typescript

npm init
npm install webpack@^3.0.0 babel-loader@8.0.0-beta.0 @babel/core @babel/preset-env @babel/preset-typescript
touch webpack.config.js .babelrc
mkdir src
touch src/app.ts

app.ts:

const a = 45

interface Cat {
    name: String,
    sex: String
}

function touchCat(cat: Cat): void {
    console.log('miao, I am ' + cat.name)
}

touchCat({
    name: 'kafe',
    sex: 'male'
})

webpack.config.js:

module.exports = {
  entry: './src/app.ts',
  output: {
    filename: '[name].[hash].js',
    path: __dirname + '/dist'
  },
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: 'babel-loader',
        exclude: '/node_modules/'
      }
    ]
  }
}

.babelrc:

{
  "presets": ["@babel/preset-env", "@babel/preset-typescript"]
}

打包后如下:

注意这样因为没有使用 tsc 编译,所以是没有类型检查的,如果需要这个功能,可以使用webpack中的 ts 类型检查插件。