webpack学习笔记

本文详细介绍了webpack的模块、核心概念,包括entry、output、module、resolve、plugin的配置,以及devServer、externals的使用。通过实例解析了如何进行优化,如CommonsChunkPlugin的公共代码提取、HotModuleReplacementPlugin的热更新、UglifyJsPlugin的代码压缩等,旨在提供一个全面的webpack学习参考。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概述

webpack是一个模块打包工具,支持CommonJs、AMD、ES6等模块化方式,并将各种静态资源都视为模块。webpack会递归地解析所有文件,构建成一个依赖关系图,最终打包成一个文件,由浏览器加载。

模块

webpack支持以下模块:

  • ES6 import语句
  • CommonJS require语句
  • AMD define、require语句
  • css/sass/less @import语句
  • 样式(url(…))或 HTML 文件(<img src=...>)中的图片链接(image url)

核心概念

  • entry: 一个可执行模块或库的入口文件。
  • chunk :多个文件组成的一个代码块,例如把一个可执行模块和它所有依赖的模块组合和一个 chunk 。这体现了webpack的打包机制。
  • loader :文件转换器,例如把es6转换为es5,scss转换为css。
  • plugin :插件,用于扩展webpack的功能,在webpack构建生命周期的节点上加入扩展hook为webpack加入功能。

配置

webpack.config.js作为配置文件。由于这个文件是在nodejs中运行的,因此不支持ES6的import语法。

const { resolve } = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  entry: {
        index: [path.resolve(__dirname, 'src/index.js')],
        vendors: ['react', 'react-dom', 'react-redux', 'react-router-dom'],
        polyfills: ['babel-polyfill'],
        libs: ['moment']

    },

  output: {
        path: path.resolve(__dirname, DevConfig.dist),
        publicPath: DevConfig.publicPath,
        filename: '[name].[hash].js'
    },

  module: {
    rules: [
          {
                test: /\.(js|jsx)$/,
                //react-hot-loader用于react热加载,即只有更改的部分会刷新,其它部分保持不变
                //要想使用热加载,除了此处之外,还需在dev-server中开启hot选项,并启用HotModuleReplacementPlugin
                loader: 'react-hot-loader!babel-loader?presets[]=es2015&presets[]=react',
                exclude: path.resolve(__dirname, 'node_modules')
            },

          {
                enforce: "pre",
                test: /\.js|jsx$/,
                exclude: /node_modules/,
                loader: "eslint-loader",
            },

          {
                test: /\.scss$/,
                loader: 'style-loader!css-loader!sass-loader'
            }

          {
                test: /\.(png|jpg|gif|woff|woff2)$/,
                loader: 'url-loader?limit=8192'
          }
    ]
  },

  plugins: [

        new webpack.optimize.CommonsChunkPlugin({
            names: ['vendors', 'polyfills', 'libs', 'manifest']
        }),
        new HtmlWebpackPlugin({
            title: 'test',
            template: path.resolve(__dirname, 'src/index.html'),
            filename: 'index.html',
            chunks: ['index', 'vendors', 'polyfills', 'libs', 'manifest'],
            inject: 'body'
        }),
        new InlineManifestWebpackPlugin({
            name: 'webpackManifest'
        }),
        new webpack.HotModuleReplacementPlugin()
  ],

  devServer: {
    port: 8100,
    historyApiFallback: true
  }
}

下面来一项一项解释。

entry

entry 参数定义了打包的入口文件。

  • 单个入口文件
    传入字符串形式的文件路径:
module.exports = {
    entry:'./src/main.js'
}
  • 多个入口文件

数组形式:将文件路径数组传给entry,数组中所有文件最终会打包生成一个文件

module.exports = {
    entry:["./src/main.js","./src/index.js]
}

对象语法:是定义入口的最可扩展的方式,最后可以从不同的入口文件出发构建成不同的文件。

{
    entry:{
        page1: "./page1",
        page2: ["./entry1", "./entry2"]
    },
    output:{
        path:'dist',
        publicPath:'/output',
        filename:'[name].bundle.js'
    }
}

上面这段代码最终会生成page1.bundle.js和page2.bundle.js,并存放在dist文件夹下。

output

配置打包输出相关。
注意,即使可以存在多个入口起点,但只指定一个输出配置。

output接受一个对象作为配置参数。

{
    output:{
        path:'dist',
        filename: 'bundle.js',
    }
}
  • path:打包文件存放的绝对路径
  • filename:打包后的文件名

因此现在打包文件将为dist/buldle.js。

正如之前在entry的配置中看到的,如果配置entry使用了对象语法,传入了多个入口文件,那么在output中可以使用占位符来确保每个打包文件有唯一的名称:

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

其中name占位符对应的是entry中相应的入口名称,此处为app与search;
而hash占位符则可以保证每次打包生成的文件有一个hash值,来防止缓存
最后两个打包文件分别为 dist/app.xxx.js 和 dist/search.xxx.js。

module

在webpack中JavaScript,CSS,LESS,TypeScript,JSX,CoffeeScript,图片等静态文件都可以是模块,都可以通过import导入。
不同模块的加载是通过相应的loader来进行编译的。

{
    module:{
        rules:[
            //loader之间用!相连,表示使用多个loader。
            //多个loader从右向左匹配,最后一个loader一定返回的是js
            {
                test:/\.scss$/,
                loader:'style-loader!css-loader!less-loader'
            },
            {
                test:/\.css$/,
                loader:'style-loader!css-loader'
            },
            //使用babel-loader处理js和jsx,其中presets[]=es2015&presets[]=react表示要使用babel-preset-es2015与babel-preset-react,分别用于转换es2015和react jsx
            //exclude用于排除node_modules目录下的文件, npm安装的包不需要编译
            //react-hot-loader用于react组件的热替换,也就是修改代码后可以实时看到变化,而无需刷新浏览器。Webpack开发服务器也需要开启HMR参数hot
            {
                test: /\.(js|jsx)$/,
                loader: 'react-hot-loader!babel-loader?presets[]=es2015&presets[]=react',
                exclude: path.resolve(__dirname, 'node_modules')
            },
            //enforce属性用于描述loader是前置、normal还是后置的
            //这里eslint-loader为前置,也就是在其他匹配js/jsx的loader前加载
            {
                enforce: "pre",
                test: /\.js|jsx$/,
                exclude: /node_modules/,
                loader: "eslint-loader",
            },
            {
                test:/\.(png|jpg)$/,
                loader:'url-loader?limit=8192'
                //url-loader接受一个limit参数, 单位为字节(byte)
                //当文件体积小于limit时, url-loader把文件转为Data URI的格式内联到引用的地方
                //当文件大于limit时, url-loader会调用file-loader, 把文件储存到输出目录, 并把引用的文件路径改写成输出后的路径
            }
        ]
    }
}

resolve

用于文件路径的解析。

alias

创建 import 或 require 的别名,来确保模块引入变得更简单

{
    resolve:{
        alias: {
            Utilities: path.resolve(__dirname, 'src/utilities/'),
            Templates: path.resolve(__dirname, 'src/templates/')
        }
    }
}

在这里为两个路径配置了别名,普通情况下我们可能这样导入模块:

import Utility from '../../utilities/utility';

现在可以这样使用别名:

import Utility from 'Utilities/utility';

也可以直接给路径下的文件配置别名:

{
    resolve:{
         alias: {
            AppStore : 'js/stores/AppStores.js',
            ActionType : 'js/actions/ActionType.js',
            AppAction : 'js/actions/AppAction.js'
        }
    }
}

这样后续直接 require(‘AppStore’) 即可。

extensions

自动扩展文件后缀名,意味着我们require模块可以省略不写后缀名

{
    resolve:{
         extensions: ['', '.js', '.json', '.scss'],
    }
}

这样我们加载js、json和scss文件时无需写后缀名了,只要 require(‘common’)就可以加载common.js文件了。

modules

告诉webpack解析模块时应该搜索的目录。
当引用模块时出现import 'vue'这样不是相对路径、也不是绝对路径的写法时,会逐级向上地去各个node_modules 目录下找。但通常项目目录里只有一个 node_modules,且是在项目根目录,为了减少搜索范围,可以直接写明 node_modules 的全路径;

resolve:{
    modules:["node_modules"]
}

plugin

plugin和loader的区别是,loader是在加载不同类型的模块时用于编译对应文件类型的工具,
而plugin是用于扩展webpack的功能,在webpack构建生命周期的节点上做相应的工作。

plugins: [
     //plugins list
 ]

一些常用的插件如下:

CommonsChunkPlugin

很常用的插件。
当我们使用的第三方文件的体积很大,或者希望提升初始加载时间时,我们希望把这些依赖的公共部分代码与业务代码分离开来,使得用户在我们更新应用时不必再次下载第三方文件。

CommonsChunkPlugin 用于提取这些依赖到共享的 bundle 中,来避免重复打包。

可以像这样添加:


    module.exports = {
        entry:{
            main:__dirname + '/app/main.js',
            vendor:['moment']
        },
        plugins: [
            new webpack.optimize.CommonsChunkPlugin({
                names:['vendor','manifest']
            });
        ]
    }

我们看到向插件的构造函数传入了两个参数vendor和manifest,以及我们在entry也加入了新的入口vendor。
moment是常用的时间处理的第三方库,也就是我们这个工程所用的公共代码。在entry处引入vendor:’moment’,最后打包时将打包出main.x.js和vendor.x.js两个文件,main.x.js文件将保存我们的业务代码,vendor.x.js将保存moment的代码。
我们在插件的构造函数中传入了vendor值,表示entry中的vendor是公共部分,打包时会被单独提取,这样我们将公共代码和业务代码进行了初步分离

在新添加的CommonmChunkPlugin插件中,我们添加了manifest值。如果不添加这个值,在打包时会发现main.x.js和vendor.x.js都会有更新,而添加manifest值可以保证公共部分的vendor不会每次被更新。

下面是另一个配置提取公共代码的例子:

module.exports = {
    entry: {
        index: [path.resolve(__dirname, 'src/index.js')],
        vendors: ['react', 'react-dom', 'react-redux', 'react-router-dom'],
        polyfills: ['babel-polyfill'],
        libs: ['moment']

    },
    plugins: [
        new webpack.optimize.CommonsChunkPlugin({
            names: ['vendors', 'polyfills', 'libs', 'manifest']
        })
    ]
}

此处的vendors,polyfills和libs都是公共部分的代码,使用CommonsChunkPlugin进行提取。

HotModuleReplacementPlugin

模块热替换功能会在应用程序运行过程中替换、添加或删除模块,而无需重新加载整个页面

CleanWebpackPlugin

用于在打包的时候删除旧文件,不会出现同一份文件存在多份的情况。

 var CleanWebpackPlugin = require('clean-webpack-plugin');
 plugins:[
    new CleanWebpackPlugin(
            ['public/main.*.js','public/manifest.*.js'],//要删除的文件目录匹配
            {
                root:__dirname,
                verbose:true,
                dry:false
            }
        )
 ]

UglifyJsPlugin

有时我们会需要压缩图片、css、js等资源。这个插件就是用于压缩js。

var UglifyJsPlugin = require('uglifyjs-webpack-plugin');
new UglifyJsPlugin({
    beautify:true,
    exclude:['/node_modules/'],
    compress:{
        warnings:false
    },
    output:{
        comments:false
    }
})

HtmlWebpackPlugin

可以生成入口html文件,其中的script标签引入了webpack打包完成的js。

var HtmlWebpackPlugin = require('html-webpack-plugin');
    new HtmlWebpackPlugin({
            title: '赛程 - CBA 数据库',
            template: path.resolve(__dirname, 'src/index.html'),
            filename: 'index.html',
            chunks: ['index', 'vendors', 'polyfills', 'libs', 'manifest'],
            inject: 'body'
    })
  • chunk属性:webpack会按照这个属性定义的数组,将数组中所有片段完成打包,并用script标签将打包的js插入到生成的页面中,没有在数组中的片段,则不插入页面

  • template:模版名称,有时我们不想直接使用插件自动生成的html文件,我们可以指定一个模版,让插件根据模版来生成html

  • inject:{true | ‘head’ | ‘body’ | false} ,注入所有的资源到特定的位置。
    如果设置为 true 或者 body,所有的 javascript 资源将被放置到 body 元素的底部,’head’ 将放置到 head 元素中。

devServer

webpack-dev-server是一个小型的Node.js Express服务器,默认情况下它将在 localhost:8080 启动一个 express 静态资源 web 服务器。

# 安装
$ npm install webpack-dev-server -g

# 运行
$ webpack-dev-server --devtool eval --progress --colors --hot --content-base build

上面指令的意思是:

  • webpack-dev-server - 在 localhost:8080 建立一个 Web 服务器
  • –devtool eval - 为你的代码创建源地址。当有任何报错的时候可以让你更加精确地定位到文件和行号
  • –progress - 显示合并代码进度
  • –colors - 命令行中显示颜色
  • –content-base build - 指向设置的输出目录
  • –hot:开启热更新功能, 参数会帮我们往配置里添加HotModuleReplacementPlugin插件
  • -d:开发环境模式,会在我们的配置文件中插入调试相关的选项, 比如打开debug, 打开sourceMap, 代码中插入源文件路径注释.
  • -p:生产环境模式, 这个模式下webpack会将代码做压缩等优化

可以把命令行写在package.json的scripts中:

{
  "scripts": {
    "dev": "webpack-dev-server -d --hot --env.dev",
    "build": "webpack -p"
  }
}

npm run时会自动寻找./node_modules/.bin/目录下的命令,因此直接执行npm run devnpm run build即可

externals

externals中配置的模块不参与打包,在运行时再从外部获取这些模块,但是依旧可以在代码中通过CommonJS、AMD、ES6或者window/global全局的方式访问。
一般用于配置由CDN引入的外部模块。
如引入jQuery:

//html中通过script引入
<script src="https://code.jquery.com/jquery-3.1.0.js"></script>
//webpack.config.js
externals:{
    jquery:'jQuery'
}

这里配置的键值对,key 是 require 的包名,value 是全局的变量。

下面的代码还是可以正常运行:

import $ from 'jquery';

$('.my-element').animate(...);

构建流程

从启动webpack构建到输出结果经历了一系列过程:

  • 解析webpack配置参数,合并从shell和webpack.config.js文件里配置的参数,生成最后的配置结果
  • 注册所有配置的插件,好让插件监听webpack构建生命周期的事件节点,以在正确的节点进行工作。
  • 从entry入口文件开始解析文件,找出每个文件依赖的文件,递归下去
  • 在解析文件的过程中根据文件类型使用对应的loader来对文件进行转换
  • 解析文件结束后,根据entry配置生成打包文件
  • 输出打包文件

几个注意点:

  • 在解析文件的过程中,如果一个文件被依赖了多次,也只会被打包一份。
    webpack打包的原理为,在入口文件中,对每个require资源文件进行配置一个id, 也
    就是说,对于同一个资源,就算是require多次的话,它的id也是一样的,所以无论在多少个文件中
    require,它都只会打包一分

  • 多入口文件,从每个入口文件开始分别打包,互相之间并不影响。因此若有两个入口文件都引用了同一个文件,这个文件会被分别打包到对应的bundle中。

  • 而针对多入口文件重复打包的问题,就可以使用CommonsChunkPlugin来优化,它原理就是把多个入口共同的依赖单独打包,这样就只打包一次

优化

打包体积优化

  • 通过externals排除从CDN引入的第三方包。
  • 针对生产环境,引入UglifyJsPlugin 来压缩代码。

打包速度优化

  • 减小搜索范围,提高查找速度:

    • 配置 resolve.modules:可以直接从根目录的node_modules中寻找模块
    • 在loaders的配置中设置include和exclude:
      对于include,更精确指定要处理的目录,这可以减少不必要的遍历,从而减少性能损失;
      同样,对于已经明确知道的不需要处理的目录,则应该予以排除,从而进一步提升性能
  • UglifyJS 优化
    Webpack 默认提供的 UglifyJS 插件,由于采用单线程压缩,速度颇慢。
    可以使用webpack-parallel-uglify-plugin来替代,该插件用于帮助具有多个入口点的项目加快其构建速度。 UglifyJS插件在每个输出文件上依次运行。 这个插件则可以并行运行UglifyJS,更加充分而合理的使用 CPU 资源,这可以大大减少的构建时间。

  • babel-loader 优化
    babel-loader非常慢,因此可以配置其cacheDirectory选项,充分利用缓存。
    之后的 Webpack 构建将尝试从缓存中读取,以避免在每次运行时运行潜在昂贵的 Babel 重新编译过程。

rules: [
  {
    test: /\.js$/,
    loader: 'babel-loader?cacheDirectory=true',
    exclude: /node_modules/,
    include: [resolve('src'), resolve('test')]
  },
  ... ...
]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值