webpack初识

1、什么是Webpack

Webpack可以看做是模块化打包工具(webpack出现,是为了解决前端模块化的问题):它做的事情是,分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript等),并将其打包为合适的格式以供浏览器使用。在3.0出现后,Webpack还肩负起了优化项目的责任。

这段话有三个重点:

  • 打包:可以把多个Javascript文件打包成一个文件,减少服务器压力和下载带宽。
  • 转换(翻译):把拓展语言转换成为普通的JavaScript,让浏览器顺利运行。
  • 优化:前端变的越来越复杂后,性能也会遇到问题,而WebPack也开始肩负起了优化和提升性能的责任。

1.1 前端模块化的优势: 1. (函数)作用域封装,避免全局作用域变量间的污染 2. 提高代码重用性 3. 降低耦合

2、为什要使用WebPack

如今的很多网页其实可以看做是功能丰富的应用,它们拥有着复杂的JavaScript代码和一大堆依赖包。为了简化开发的复杂度,前端社区涌现出了很多好的实践方法:

a:模块化,让我们可以把复杂的程序细化为小的文件;

b:类似于TypeScript这种在JavaScript基础上拓展的开发语言,使我们能够实现目前版本的JavaScript不能直接使用的特性,并且之后还能转换为JavaScript文件使浏览器可以识别;

c:scss,less等CSS预处理器。

3. webpack打包过程

  1. 从入口文件(src/index.js)开始,分析整个应用的依赖树。
  2. 将每个依赖模块包装起来,放到一个数组(立即执行函数的参数)中等待调用。
  3. 实现模块加载(_webpack_require_)的方法,并把它放到模块执行环境中,确保模块之间可以相互调用。
  4. 把执行入口文件的逻辑放到一个函数表达式中,并立即执行这个函数。
function (modules) {
    var installedModules = {};

    function __webpack_require__ (moduleID) {
    // code
    }
    // ...
    __webpack_require__(0)  // 加载入口模块
})([/* 2. modules array */])

 

entry

  分离 应用程序(app) 和 第三方库(vendor) 入口。

从表面上看,这告诉我们 webpack 从 app.js 和 vendors.js 开始创建依赖图(dependency graph)。这些依赖图是彼此完全分离、互相独立的。常见于,只有一个入口起点(不包括 vendor)的单页应用程序(single page application)中.

1 // webpack.config.js
2 const config = {
3   entry: {
4     app: './src/app.js',
5     vendors: './src/vendors.js'
6   }
7 };

有时可以选择将项目的基础依赖(一定会用到的,如vue)摘出来,也加到entry中。参考:https://zhuanlan.zhihu.com/p/32093510

entry: {
    app: path.join(__dirname, 'main.js'),
    vendor: ['vue']
}

之后加载的类库可以使用commonsChunkPlugin添加(webpack4中 webpack.optimize.CommonsChunkPlugin 被 optimization.splitChunks 替代)

plugins: [
    //...此前的代码
    new webpack.HashedModuleIdsPlugin(),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      minChunks: Infinity,
    }),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'common',
      minChunks: function(module) {
        return (
          module.resource &&
          /\.js$/.test(module.resource) &&
          module.resource.indexOf(
            path.join(__dirname, './node_modules')
          ) === 0
        )
      },
      chunks: ['index'],
    }),
    new webpack.optimize.CommonsChunkPlugin({
        name: 'manifest',
        chunks: ['vendor', 'common', 'index']
    })
]   

(插件 HashedModuleIdsPlugin,是用于保持模块引用的 module id 不变。而 CommonsChunkPlugin 则提取入口指定的依赖独立打包,minChunks: Infinity 的用意是让插件别管其他,就按照设置的数组提取文件就好。另一个chunk命名为 common,指定它从入口 index.js 中抽取来自 node_modules 的依赖(可变依赖,区别于vendor)。最后就是抽取 webpack 运行时的函数及其模块标识组成 manifest。)

  多页面应用程序

下面的代码中,webpack 构建了3 个独立分离的依赖图。使用 optimization.splitChunks 可为每个页面间的应用程序共享代码创建 bundle。由于入口起点增多,多页应用能够复用入口起点之间的大量代码/模块。

1 // webpack.config.js
2 const config = {
3   entry: {
4     pageOne: './src/pageOne/index.js',
5     pageTwo: './src/pageTwo/index.js',
6     pageThree: './src/pageThree/index.js'
7   }
8 };

output 

  • path 目标输出目录的绝对路径。
  • filename 用于输出文件的文件名。

  A. 对于单个入口起点,filename 会是一个静态名称。

filename: "bundle.js"

  B. 然而,当通过多个入口起点(entry point)、代码拆分(code splitting)或各种插件(plugin)创建多个 bundle,应该使用以下一种替换方式,来赋予每个 bundle 一个唯一的名称。

  1. 使用入口名称:
    filename: "[name].bundle.js"
  2. 使用内部 chunk id:
    filename: "[id].bundle.js"
  3. 使用每次构建过程中,唯一的 hash 生成
    filename: "[name].[hash].bundle.js"
  4. 使用基于每个 chunk 内容的 hash
    filename: "[name].[chunkhash].js"
  • publicPath 按需加载或外部加载资源时的目录,可以是绝对路径或相对路径。
publicPath: "https://cdn.example.com/assets/", // CDN(总是 HTTPS 协议)
publicPath: "//cdn.example.com/assets/", // CDN (协议相同)
publicPath: "/assets/", // 相对于服务(server-relative)
publicPath: "assets/", // 相对于 HTML 页面
publicPath: "../assets/", // 相对于 HTML 页面
publicPath: "", // 相对于 HTML 页面(目录相同)
  • library 与libraryTarget配合使用
  • libraryTarget  默认选项是 var. 
    output: {
      library: "MyLibrary"
    }
    //
    如果生成的输出文件,是在 HTML 页面中作为一个 script 标签引入,则变量 MyLibrary 将与入口文件的返回值绑定。

   单一入口起点

// webpack.config.js

const config = {
  output: {
    filename: 'bundle.js',
    path: '/home/proj/public/assets'
  }
};

module.exports = config;

  多个入口起点

{
  entry: {
    app: './src/app.js',
    search: './src/search.js'
  },
  output: {
    filename: '[name].js',
    path: path.join(__dirname, '/dist')
  }
}

// 写入到硬盘:./dist/app.js, ./dist/search.js

mode(模式)分为两种:development和production

1 // webpack.config.js
2 
3 const config = {
4   mode: 'production'
5 };
6 module.exports = config;
选项
描述
development
会将 process.env.NODE_ENV 的值设为 development。启用 NamedChunksPlugin 和 NamedModulesPlugin
production

会将 process.env.NODE_ENV 的值设为 production。启用 FlagDependencyUsagePluginFlagIncludedChunksPlugin,

 ModuleConcatenationPluginNoEmitOnErrorsPluginOccurrenceOrderPluginSideEffectsFlagPlugin 和 UglifyJsPlugin

压缩输出:从 webpack 4 开始,可以通过 "mode" 配置选项设置为 "production",轻松实现压缩输出。

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
    },
 mode: "production"
};

loader

 webpack 自身只理解 JavaScript. loader 可以将所有类型的文件转换为 webpack 能够处理的有效模块,然后就可利用 webpack 的打包能力对它们进行处理。本质上,webpack loader 将所有类型的文件,转换为应用程序的依赖图(和最终的 bundle)可以直接引用的模块

可以使用属性 rules 和 oneOf 指定嵌套规则。

// webpack.config.js
const path = require('path');

const config = {
  output: {
    filename: 'my-first-webpack.bundle.js'
  },
  module: {
    rules: [
      { 
    test:
/\.txt$/,
    use: 'raw-loader',
    include: path.resolve(__dirname, "src)
}
]
 }
};

module.exports = config;
{
  test: /.css$/,
  oneOf: [   //当规则匹配时,只使用第一个匹配规则
    {
      resourceQuery: /inline/, // foo.css?inline
      use: 'url-loader'
    },
    {
      resourceQuery: /external/, // foo.css?external
      use: 'file-loader'
    }
  ]
}
  1. test 字段,用于标识出应该被对应的 loader 进行转换的某个或某些文件。
  2. use 字段,表示进行转换时,应该使用哪个 loader。
  3. include 字段,仅将 loader 模块应用在实际需要用其转换的路径下。
在 webpack 配置中定义 loader 时,要定义在 module.rules 中,而不是 rules。

rules条件有两种输入值:

  1. resource:请求文件的绝对路径。它已经根据 resolve 规则解析。

  2. issuer: 被请求资源(requested the resource)的模块文件的绝对路径。是导入时的位置。

例如: 从 app.js 导入 './style.css',resource 是 /path/to/style.css. issuer 是 /path/to/app.js

  在规则中,属性 testincludeexclude 和 resource 对 resource 匹配,而属性 issuer 对 issuer 匹配。

plugins(插件)

功能强大,包括打包优化、压缩、重新定义环境中的变量等。

想要使用一个插件,你只需要 require() 它,然后把它添加到 plugins 数组中。多数插件可以通过选项(option)自定义。你也可以在一个配置文件中因为不同目的而多次使用同一个插件,这时需要通过使用 new 操作符来创建它的一个实例。

const webpack = require('webpack'); // 用于访问内置插件
// 导入非 webpack 自带默认插件
// 通过 npm 安装
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var DashboardPlugin = require('webpack-dashboard/plugin');

const config = {
  module: {
    rules: [
      { test: /\.txt$/, use: 'raw-loader' }
    ]
  },
  plugins: [
// UglifyJSPlugin被optimization.minimize替代
  new webpack.optimize.UglifyJsPlugin({
    compress: {
        warnings: false,
        drop_console: false,
      }
    }),
// extractTextPlugin被替代(见本文最后)   
new ExtractTextPlugin({    filename: 'build.min.css',   allChunks: true,   }),   new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),   // 编译时(compile time)插件:创建编译时可配置的全局常量   new webpack.DefinePlugin({   'process.env.NODE_ENV': '"production"',   }),   // webpack-dev-server 强化插件   new DashboardPlugin(),   new webpack.HotModuleReplacementPlugin(), ] }; module.exports = config;
  • ProvidePlugin:自动加载模块,而不必使用import或者require。

 

// webpack.config.js
new
webpack.ProvidePlugin({ $: 'jquery', jQuery: 'jquery' });

 

之后可在我们的代码中随意使用$和jQery,不需要引入。

 

devServer  https://www.webpackjs.com/configuration/dev-server/

  allowedHosts 允许访问devServer的白名单。

allowedHosts: [
  'host.com',
  'subdomain.host.com',
  'subdomain2.host.com',
  'host2.com'
]

  before函数 优先于其他,进行handler定义。

before(app){
  app.get('/some/path', function(req, res) {
    res.json({ custom: 'response' });
  });
}

  compress 一切服务都使用gzip压缩。

compress: true

  contentBase 提供静态文件时的目录(推荐使用绝对路径)。

默认情况下,将使用当前工作目录作为提供内容的目录,但可修改为其他目录:

contentBase: path.join(__dirname, "public")

也可以从多个目录提供内容:

contentBase: [path.join(__dirname, "public"), path.join(__dirname, "assets")]

  watchContentBase 文件改变将触发整个页面的重载。

  lazy 是否开启惰性模式当启用 lazy 时,dev-server 只有在请求时才编译包(bundle)。这意味着 webpack 不会监视任何文件改动。

 

  filename 在惰性模式下,使用filename可以减少编译。

lazy: true,
filename: "bundle.js"

只有在请求 /bundle.js 时候,才会编译 bundle。

  header 在所有响应中添加首部内容。

headers: {
  "X-Custom-Foo": "bar"
}

  host 默认是 localhost。如果你希望服务器外部可访问,指定如下:

host: "0.0.0.0"

  port 指定要监听请求的端口号.

port:8080

  hot 是否启用模块热替换的特性。

  https devServer是否提供https服务。默认情况下,dev-server 通过 HTTP 提供服务。

  proxy 代理。如果在 localhost:3000 上有后端服务的话,你可以这样启用代理:

proxy: {
  "/api": "http://localhost:3000"
}

请求到 /api/users 现在会被代理到请求 http://localhost:3000/api/users.

如果不想始终传递 /api ,则需要重写路径:

proxy: {
  "/api": {
    target: "http://localhost:3000",
    pathRewrite: {"^/api" : ""}
  }
}

若想要代理多个接口到同一后端,可通过context后连接一个数组。

proxy: [{
  context: ["/auth", "/api"],
  target: "http://localhost:3000",
}]

devtool:  

开发环境

eval - 每个模块都使用 eval() 执行,并且都有 //@ sourceURL。此选项会非常快地构建。主要缺点是,由于会映射到转换后的代码,而不是映射到原始代码(没有从 loader 中获取 source map),所以不能正确的显示行数.

eval-source-map - 每个模块使用 eval() 执行,并且 source map 转换为 DataUrl 后添加到 eval() 中。初始化 source map 时比较慢,但是会在重新构建时提供比较快的速度,并且生成实际的文件。行数能够正确映射,因为会映射到原始代码中。它会生成用于开发环境的最佳品质的 source map。

cheap-eval-source-map - (类似 eval-source-map)每个模块使用 eval() 执行。这是 "cheap(低开销)" 的 source map,因为它没有生成列映射(column mapping),只是映射行数。它会忽略源自 loader 的 source map,并且仅显示转译后的代码,就像 eval devtool。

cheap-module-eval-source-map - 类似 cheap-eval-source-map,并且,在这种情况下,源自 loader 的 source map 会得到更好的处理结果。然而,loader source map 会被简化为每行一个映射(mapping)。

生产环境

(none)(省略 devtool 选项) - 不生成 source map。这是一个不错的选择。

source-map - 整个 source map 作为一个单独的文件生成。它为 bundle 添加了一个引用注释,以便开发工具知道在哪里可以找到它。

应将服务器配置为,不允许普通用户访问 source map 文件!

hidden-source-map - 与 source-map 相同,但不会为 bundle 添加引用注释。如果你只想 source map 映射那些源自错误报告的错误堆栈跟踪信息,但不想为浏览器开发工具暴露你的 source map,这个选项会很有用。

你不应将 source map 文件部署到 web 服务器。而是只将其用于错误报告工具。

nosources-source-map - 创建的 source map 不包含 sourcesContent(源代码内容)。它可以用来映射客户端上的堆栈跟踪,而无须暴露所有的源代码。你可以将 source map 文件部署到 web 服务器。

 注意问题:

 1.混合使用ES2015,AMD CommonJS

你可以自由混合使用三种模块类型(甚至在同一个文件中)。在这个情况中 webpack 的行为和 babel一致。

// ES2015 模块调用 CommonJS
import fs from "fs"; // module.exports 映射到 default
import { readFileSync } from "fs"; // 从返回对象(returned object+)中读取命名的导出方法(named exports)

typeof fs.readFileSync === "function";
typeof readFileSync === "function";

需要让 Babel 不解析这些模块符号,从而让 webpack 可以使用它们。可以通过设置如下配置到 .babelrc

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

2.只在production环境下才会用到的工具(在development时应避免)

  • UglifyJsPlugin(webpack4中,UglifyJsPlugin 被内置,只需optimization.minimize:true,且在production mode下面自动为true)
  • ExtractTextPlugin(webpack中 使用mini-css-extract-plugin代替)
  • [hash]/[chunkhash]
  • AggressiveSplittingPlugin
  • AggressiveMergingPlugin
  • ModuleConcatenationPlugin

升级到webpack 4必读 webpack一键升级

resolve 

以下建议可以提高解析速度

  • 尽量减少 resolve.modulesresolve.extensionsresolve.mainFilesresolve.descriptionFiles 中类目的数量,因为他们会增加文件系统调用的次数。
  • 如果不使用 symlinks ,可以设置 resolve.symlinks: false.
  • 如果使用自定义解析 plugins ,并且没有指定 context 信息,可以设置 resolve.cacheWithContext: false 。

 webpack 5

1. scalability

持久缓存

webpack工作原理:webpack读取入口文件(entry),然后递归查找所依赖的模块(module),构建成一个“依赖图”,然后根据配置中的加载器(loader)打包策略来对模块进行编译。如果中间有文件发生变化了,上面所述的整个递归遍历流程会重新再进行一次。

webpack 5利用持久缓存优化了整个流程,当检测到某个文件变化时,依照“依赖图”,只对修改过的文件进行编译,从而大幅提高了编译速度。

引入了更多新东西

webpack 5 要求node的最低版本为 Node 8。

另外,webpack 5还引入了webAssembly、Hashing、多线程、还有workers。

NodeJS的polyfill脚本被移除.

posted @ 2019-03-18 17:27  cecelia  阅读(319)  评论(0编辑  收藏  举报