乐趣区

webpack-从入门到放弃

webpack 从入门到放弃

随着前端项目复杂程度越来越高,依赖也越来越多,为了提高项目中代码的可复用性,前端开始提出模块化开发的思路,前端模块化会有以下几个痛点:

  1. 命名冲突
  2. 文件依赖
  3. 代码复用

模块化,会将相关的代码封装成一个 package 包的文件,当需要的时候,直接拿来用即可 (import 引入)。
至于相关文件的直接依赖如何处理,webpack 会帮我们解决这个问题。

那么模块化应该使用什么样的规范呢?常见的模块化的标准主要如下:

  • commonJs

    nodejs 使用的就是 commonJs 的规范,采用的 同步方式 加载依赖,一个文件就是一个模块。

    导入导出方式如下:

    导入:const aa = require('./aa')
    导出:module.exports = aa.fun

    缺点是加载的模块是同步的,只有加载完才能执行后面的操作。因为 node.js 的模块文件一般存在于本地硬盘,所以一般不会出现这个问题,但是在浏览器环境该规范就不那么适用了。

  • AMD

    因为 commonJS 不适用于浏览器的环境,所以出现 AMD 规范。特点: 异步加载 / 推崇依赖前置、提前执行 。require.js 最佳实践

    使用方式:

    定义模块:define()
    加载模块:require()
    引用:require.config()
  • CMD

    CMD 的使用方式如上,但是特点不太一样。CMD 是 异步加载 / 依赖就近、延迟执行 sea.js 最佳实践

  • ES6

    ES6 直接在语言层面上实现了模块化。我们现在用的最多的就是 ES6 模块化的实践方式。

    导入导出方式如下:

    导入:import aa from 'aa'
    import {A} from 'aa'
    
    导出:export default {A, B}
    export {A, B}
    export funtion aa() {}

    虽然 ES6 是模块化的最终方案,但是还是在许多浏览器上有兼容问题,需要对代码进行转码处理才行。

  • 样式文件中的模块化

    前端开发中,样式文件也开始支持模块化,以 stylus 为例,通常将一些通用的文件放到文件中,在使用相关的样式时,直接 @import 引入文件,就能使用对应的代码片段, 当然也能定义一些全局的参数 (颜色,圆角类) 进行使用。

    // common.styl
    .center-transform
      top 50%
      left 50%
      transform translate(-50% -50%)
    center-transform()
      top 50%
      left 50%
      transform translate(-50% -50%)
    
    // 使用
    @import 'common.styl'
    .box {center-transform}
    

webpack 和 Gulp/Grunt、Rollup 比较

  • Grunt: 是一套前端自动化工具,帮助处理反复重复的任务。一般用于:编译,压缩,合并文件,简单语法检查等
  • Gulp: 是基于“流”的自动化构建工具,采用代码优于配置的策略,更易于学习和使用
  • Rollup: Rollup 是一个和 Webpack 很类似但专注于 ES6 的模块打包工具。Rollup 的亮点在于能针对 ES6 源码进行 Tree Shaking 以去除那些已被定义但没被使用的代码,以及 Scope Hoisting 以减小输出文件大小提升运行性能。但是 Rollup 生态链还不完善,体验不如 Webpack。
  • webpack:Webpack 是模块化管理工具和打包工具。通过 loader 的转换,任何形式的资源都可以视作模块,比如 CommonJs 模块、AMD 模块、ES6 模块、CSS、图片等。它可以将许多松散的模块按照依赖和规则打包成符合生产环境部署的前端资源。还可以将按需加载的模块进行代码分隔,等到实际需要的时候再异步加载

开始使用 webpack

首先创建一下的目录结构:

.
├── src
│   ├── assemble
│   │   ├── css
│   │   │   ├── index.css
│   │   ├── index.html
│   │   └── js
│   │       ├── index.js
│   │       └── test.js
│   ├── babyTest
│   │   ├── css
│   │   │   └── index.css
│   │   ├── index.html
│   │   └── js
│   │       └── index.js


文件内容:
// 1. assemble-index.html: 
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title></title>
</head>
<body>
</body>
</html>

// 2. assemble-css/index.css:
body {background: red} 

// 3. assemble-js/index.js:
import '../css/index.css'
console.log('index.js')


// 4. assemble-js/test.js:
console.log('test.js')

babyTest 目录和上面类似
  1. 创建 package.json 文件: npm init
  2. 安装 webpack(全局、目录安装):npm install webpack -D
  3. 创建 webpack.config.js 文件

    下面直接配置的是多文件入口:// 引入插件
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    // 相关配置
    module.exports = {
     mode: 'development', // 指定 webpack 的模式  
     entry: {
       'assemble/js/index': './src/assemble/js/index.js',
       'assemble/js/test': './src/assemble/js/test.js',
       'babyTest/js/index': './src/babyTest/js/index.js'
     },
     output: {filename: '[name].js',  // 打包后输出文件的文件名
       path: path.resolve('./output/market') // 打包后的文件存放的地方
     },
     module: {
       rules: [
         {
           test: /\.css$/,
           use: ['style-loader', 'css-loader']
         }
       ]
     },
     plugins: [
       new HtmlWebpackPlugin({
         template: './src/assemble/index.html',
         filename: 'assemble/index.html',
         chunks: ['assemble/js/index']
       }),
       new HtmlWebpackPlugin({
         template: './src/babyTest/index.html',
         filename: 'babyTest/index.html',
         hash: true,
         chunks: ['babyTest/js/index']
       })
     ]
    }

    上面写的实例中,将 loader 和 plugins 已经使用了,可以先按照上面的使用,后边将进行详细方法的介绍。

    使用前,安装对应的依赖包

    npm install css-loader style-loader html-webpack-plugin -D

    备注:

    mode,参考 模式

    多文件入口配置,参考 多页面应用程序

  4. 终端执行打包命令:webpack

可以看到在根目录下有 output 目录,里面就行对应的打包文件。直接运行 output 文件下的目录,能够看到对应效果,css 和 js 都能够执行成功。

备注:
执行打包命令的方式有以下几种方式

  1. node_modules/.bin/webpack
  2. webpack
  3. 在 package.json 文件中配置对应的命令 “scripts”: {“start”: “webpack”}

entry

入口起点 (entry point) 指示 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始。进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。

每个依赖项随即被处理,最后输出到称之为 bundles 的文件中,,entry 属性 来指定一个入口起点(或多个入口起点)。默认值为 ./src。

output

output 属性告诉 webpack 在哪里输出它所创建的 bundles,以及如何命名这些文件,默认值为 ./dist。基本上,整个应用程序结构,都会被编译到你指定的输出路径的文件夹中。

Loader

loader 用于对模块的源代码进行转换。loader 可以使你在 import 或 ” 加载 ” 模块时预处理文件。因此,loader 类似于其他构建工具中“任务(task)”,并提供了处理前端构建步骤的强大方法。loader 可以将文件从不同的语言(如 TypeScript)转换为 JavaScript,或将内联图像转换为 data URL。loader 甚至允许你直接在 JavaScript 模块中 import CSS 文件!

上面的配置中就用到了 css-loaderstyle-loader, 主要是在对引用的 css 文件会用style-loadercss-loader进行处理,当配置多个 loader 的时候,按照从左至右的方式进行处理。

Loaders 的配置主要包含以下配置:

  1. test(必填): 用正则匹配对应文件的扩展名
  2. use(必填):loader 的名称
  3. include/exclude(非必填):手动添加必须处理或者是忽略的文件
  4. query(非必填):为 loaders 提供额外的设置选项

在 webpack 中使用的方式

  1. 命令行调用:webpack –module-bind jade –module-bind ‘css=style!css’
  2. 通过 require:require(‘style-loader!css-loader?minimize!./main.css’)
  3. 使用配置文件:如上配置

联系紧密的Babel

Babel 是一个编译 JavaScript 的平台,可以帮我们将编译的代码

  • 让你能使用最新的 JavaScript 代码(ES6,ES7…),而不用管新标准是否被当前使用的浏览器完全支持
  • 让你能使用基于 JavaScript 进行了拓展的语言

Babel 的安装与使用

  1. Babel 的安装

Babel 其实是几个模块化的包,其核心功能称为 babel-core 的 npm 包中,webpack 可以把其不同的包整合在一起使用,对于每一个你需要的功能或拓展,你都需要安装单独的包(用的最多的就是解析 ES6 语法的包 babel-preset-env)
npm install --save-dev babel-core babel-loader babel-preset-env

在 webpack.config.js 中,module 的 rules 中添加;

{
  test: /\.js$/,
  use: {
    loader: 'babel-loader',
    options: {
      presets: ['env', 'react']
    }
  },
  include: /src/,
  exclude: /node_modules/
}
  1. Babel 的配置

Babel 的配置可以直接在 webpack.config.js 中进行配置,但是有时配置项过多,但在这个文件中配置过多 js 编译规则的代码,显得可读性不太好,因此支持在 .babelrc 中单独配置,webpack 会自动调用这个文件中的配置项。

// .babelrc 文件
{"presets": ["react", "env"]
}

loader 的特性(引用官网)

  • loader 支持链式传递。能够对资源使用流水线(pipeline)。一组链式的 loader 将按照相反的顺序执行。loader 链中的第一个 loader 返回值给下一个 loader。在最后一个 loader,返回 webpack 所预期的 JavaScript。
  • loader 可以是同步的,也可以是异步的。
  • loader 运行在 Node.js 中,并且能够执行任何可能的操作。
  • loader 接收查询参数。用于对 loader 传递配置。
  • loader 也能够使用 options 对象进行配置。
  • 除了使用 package.json 常见的 main 属性,还可以将普通的 npm 模块导出为 loader,做法是在 package.json 里定义一个 loader 字段。
  • 插件 (plugin) 可以为 loader 带来更多特性。
  • loader 能够产生额外的任意文件。

扩展 - 常使用的 loader 清单

loader name 介绍
css-loader 解析 @import 和 Url() 引用的 css 文件,转化成 import/require() 的方式然后在解析他们
style-loader 将 CSS 放在 <style></style> 标签中注入到 DOM 中
sass-loader 将 SASS/SCSS 转换为 css
less-loader 将 less 转化为 css
html-loader 将 html 输出为字符串,也可以根据配置进行压缩
babel-loader 加载 ES2015+ 代码,然后使用 Babel 转译为 ES5
url-loader 将文件加载为 Base64 编码的 URL
file-loader 可以解析项目中的 url 引入(不仅限于 css),根据我们的配置

… 更多请查看上面的链接

plugin

插件是 webpack 的支柱功能。webpack 自身也是构建于,你在 webpack 配置中用到的相同的插件系统之上!

插件目的在于解决 loader 无法实现的其他事。

Plugins 在构建的过程中,通过在构建流程中注入对应的钩子,给 webpack 带来很大的灵活性。

Loaders 和 Plugins 常常被弄混,但是他们其实是完全不同的东西,可以这么来说,loaders 是在打包构建过程中用来处理源文件的(JSX,Scss,Less..),一次处理一个,Plugins 并不直接操作单个文件,它直接对整个构建过程其作用

上面的实例中就使用了 html-webpack-plugin 插件,主要的功能就是自动引用使用的 js 文件,使用 css 的话,也会根据一定规则进行动态插入,在控制台查看文件代码时,可以看到引用的 css 文件,会自动插在 <head></head> 之间。如截图所示:

我们可以根据自己的项目构建方式,使用对应的插件,就不一一举例说明了。

扩展 - 常用的 Plugins 清单

Plugins name 介绍
HtmlWebpackPlugin 简单创建 HTML 文件,用于服务器访问
clean-webpack-plugin 删除指定的文件夹
webpack-bundle-analyzer webpack 打包体积查看,便于优化文件体积
speed-measure-webpack-plugin 查看测量出在你的构建过程中,每一个 Loader 和 Plugin 的执行时长(优化 webpack 时能帮你看出对应的时间)

… 更多优秀插件请查看清单链接

启动静态服务器

启动一个静态服务器,默认会自动刷新,就是说你对 html,css,js 文件做了修改并保存后,浏览器会默认刷新一次展现修改后的效果

正常情况下我们都是在开发环境中开发项目,所以之前配置的脚本 ”dev” 可以派上用场了,在执行 npm run dev 命令后,会启动静态服务器,我们访问 localhost:3000 端口就可以看到开发的页面内容了

对应的配置文件:

// webpack.config.js 的配置文件
devServer: {
  host: 'localhost',  // 默认是 localhost
  port: '3000',   // 端口
  open: true,    // 自动打开
  hot: true,    // 开启热更新
  openPage: 'assemble'  // 默认打开的文件
}

// package.json 中的配置
"scripts": {"dev": "webpack-dev-server --open"}

思考:热更新和自动更新的区别??(待补充 …)

开发、测试、上线构建的细微区别

通常本地开发,测试和线上的构建方式略微有点区别,线上文件都会将文件中的空行,空格,换行清除,并且压缩 html,css,js 和对应 image 的体积,这样就能保证体积最小,加快页面的加载速度。

但是开发和测试环境通常,在浏览器中,无法对代码逐行打断点进行调试,所有需要使用 source maps 进行调试,它使得我们在浏览器中可以看到源代码,进而逐行打断点调试。

使用 source maps 方法

在 webpack.config.js 文件中,添加 devtool 属性,赋值为 source-map或者 inline-source-map 即可,后者报错信息更加具体,会指示源代码中的具体错误位置,而 source-map 选项无法指示到源代码中的具体位置。

webpack 优化

1. 代码编写过程中遇到的现象

问题描述:更改一个文件,导致所有的文件都会重新进行编译。

  • 修改前:
  • 修改后:

优化方案:使用缓存

  1. 安装 cache-loader 模块 npm install cache-loader -D
  2. 在 module.rules 对应的 loader 最前面添加cache-loader

参考文章

浅入浅出 webpack

webpack 从入门到放弃(一)

入门 Webpack,看这篇就够了

退出移动版