webpack学习笔记

webpack预备知识

webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle

webpack配置文件

webpack.config.js

webpack的核心概念

  • 入口(entry)
  • 输出(output)
  • loader
    webpack 开箱即用只支持JSJSON两种文件类型,通过Loaders去支持其它文件类型并且把它们转化成有效的模块,并且可以添加到依赖图中。本身是一个函数,接受源文件作为参数,返回转换的结果。(如:babel-boader、css-loader)
  • 插件(plugins)
    插件⽤于bundle⽂件的优化,资源管理和环境变量注⼊作⽤于整个构建过程。(如:CommonsChunkPlugin、CleanWebpackPlugin)
  • Mode
    Mode ⽤来指定当前的构建环境是:productiondevelopment还是none
    设置mode可以使⽤webpack内置的函数,默认值为production

有三个比较容易混淆的概念,bundle,chunk和module。

  • bundle:打包最终生成的文件
  • chunk:每个chunk是由多个module组成,可以通过代码分割成多个chunk
  • modulewebpack中的模块

demos

下面我们通过一系列demo一步步学习和实践webpack的基本使用。

demo01:入口和输出

webpack根据配置文件中的entry找到工程的入口文件,加载入口文件中定义的依赖进行打包,并将最终的文件输出到output配置项定义的位置。

entry: './index.js',
output: {
    filename: 'bundle.js',
    path: __dirname + '/dist'
}

demo02:多入口

webpack支持配置多个入口,并对不同入口进行命名。

entry: {
    entry1: './entry1.js',
    entry2: './entry2.js'
},
output: {
    filename: '[name].bundle.js',  // name即为入口名
    path: __dirname + '/dist'
}

demo03:调整目录结构

demo2中,源文件和打包后的文件混杂在一起,看起来很乱。我们将源文件和输出文件分开,调整目录结构如下:

|-dist
|-src
    |-content.js
    |-index.js
|-package.json
|-webpack.config.js

demo04:自动生成html入口文件

当前我们在dist目录下的index.html文件手动引入所有资源,当打包规则渐趋复杂后,这一过程将变得难以维护。实际上,html入口文件和其他输出文件一样,应该由webpack生成,并自动添加依赖的资源;HtmlWebpackPlugin插件可以完成这一自动化过程。

安装HtmlWebpackPlugin插件 npm i html-webpack-plugin --save-dev

修改webpack.config.js

const path = require('path');
// 引入HtmlWebpackPlugin插件
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports =  {
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'bundle.js'
    },
    plugins: [
        // 自动生成html文件到dist目录
        new HtmlWebpackPlugin({
            template: './src/index.html',   // 源文件
            filename: 'index.html'          // 生成文件名    
        })
    ]
};

demo05:解析ECMASCript 6 和 React JSX

使用ES6react进行开发,我们需要依赖babel进行代码的转换。

解析ES6要借助babel-loader
增加ES6babel preset配置(.babelrc的配置文件-presetsplugins

  • step01: 安装@babel/core(解析es6)、@babel/preset-env(解析es6)、babel-loader(解析js文件)
  • step02: 安装语法:npm i @babel/core @babel/preset-env babel-loader -D

.babelrc文件的介绍:

{
    "presets": [
      "@babel/preset-env”
    ],
    "plugins": [
        "@babel/proposal-class-properties"
    ]
}

plugin用来支持某个功能,presets是多个plugin的集合。就是这么简单

解析react要借助@babel/preset-react

  • step01: 安装reactreact-dom(react)、preset-react(解析react)
  • step02: 安装语法:npm i react react-dom @babel/preset-react -D

添加loader配置

module: {
    rules: [{
        test: /\.jsx?$/, 
        use: [{
            loader: 'babel-loader',
            options: {
                presets: [
                    ["react"],
                    ["env"]
                ]
            }
        }],
        exclude: /node_modules/
    },
    ...
    ]
}

demo06:加载静态资源

作为一个module bundlewebpack可以将所有类型的资源处理为模块。不过,webpack本身只理解javaScript;对于非javascript模块,需要使用相应的loader进行转换。

**解析CSS、Less和Sass

作为一个module bundlewebpack可以将所有类型的资源处理为模块。不过,webpack本身只理解javaScript;对于非javascript模块,需要使用相应的loader进行转换。
css-loader ⽤于加载.css⽂件,并且转换成 commonjs 对象
style-loader 将样式通过

安装语法:npm i style-loader css-loader -D&& npm i less less-loader -D

添加loader配置

{
  test: /\.css$/,
  use: [
    'style-loader', //链式调用,从右向左,先解析css,再将解析好的css传递给style-loader
    'css-loader',
    'less-loader'
    ]
  },
  {
    test: /\.less$/,
    use: [
      'style-loader',
      'less-loader'
    ]
}
**解析图片和字体

file-loader ⽤于处理⽂件,也可以解析字体

安装语法: npm i file-loader -D

url-loader 也可以处理图⽚和字体,可以设置较⼩资源⾃动 base64,直接打包进js文件中,不会再打包图片
安装语法: npm i file-loader -D

{
  test: /\.(png|jpg|gif|jpeg)$/,
  use: [
    'file-loader',
  ]
},
{
  test: /\.(png|jpg|gif|jpeg)$/,
  use: [
    {
      loader: 'url-loader',
      options: {
        limit: 10240 //图片小于10k,打包时自动base64
      }
    }
  ]
},
{
  test: /\.(woff|woff2|eot|ttf|otf)$/,
   use: [
      'file-loader',
    ]
}

demo07:source-map和dev-server

当我们使用webpack打包后的文件时,代码的调试成为一件困难的事情;一旦代码出现错误和警告,我们很难追踪到它们的源代码中的具体位置。为了解决这一问题,可以使用source-map功能,将编译后的代码映射回原始代码

作用:通过source map定位到源代码
使用:开发环境开启,线上环境关闭(线上排查问题的时候可以将sourcemap上传到错误监控系统)

source map 关键字

  • eval: 使用eval包裹模块代码
  • source map: 产生.map文件
  • cheap: 不包含列信息,只能看到行
  • inline: 将.map作为DataURI嵌入,不单独生成.map文件(但是单个文件打包体积较大)
  • module: 包含loadersourcemap

案例:

// sourc-map
devtool: 'inline-source-map'

webpack-dev-server提供了一个简单的web服务,能够监听源代码的变化进行实时编译,并自动刷新浏览器页面

安装npm依赖 npm i webpack-dev-server --save-dev

修改webpack.config.js,增加dev-server配置项

devServer: {
    contentBase: './dist'  // webpack-dev-server可访问文件目录
}

为了方便,我们在package.json添加一个npm脚本

"scripts": {
    "start": "webpack-dev-server --open"
    ...
}

执行npm run startwebpack-dev-server会自动打开浏览器,加载dist文件夹下的index.html文件;修改源文件并保存,服务器将重新编译源文件并刷新页面

demo08:公共模块提取(提取页面公共资源)

1、基础库的分离
  • 思路: 将reactreact-dom基础包通过cdn引入,不打入bundle
  • 方法: 使用html-webpack-externals-plugin

图片说明:

2、利用SplitChunksPlugin进行公共脚本分离

假设我们正在开发一个多页面应用,该应用包含主页(index)和关于(about)两个页面。很自然的,我们会针对两个页面分别设置入口和html源文件:

entry: {
    index: './src/index.js',
    about: './src/about.js'
},
output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].bundle.js'
}
plugins: [
    new HtmlWebpackPlugin({
        template: './src/index.html',   // 源文件
        filename: 'index.html'         // 生成文件名    
    }),
    new HtmlWebpackPlugin({
        template: './src/about.html',
        filename: 'about.html'
    })
]

在这种多入口场景下,不同的入口文件往往会依赖一些共同的模块;这些重复的模块会被打包到各个bundle,增大了输出文件的体积。webpack3使用CommonsChunkPlugin插件将公共的依赖模块提取到一个独立的bundle,供多个页面重用(如通过缓存)。但是目前已废弃,webpack4利用SplitChunksPlugin进行公共包的分离

chunks参数说明:

  • async 异步引入的库进行分离(默认,如import引入库)
  • initial 同步引入的库进行分离
  • all 所有引入的库进行分离(推荐)
module.exports = {
    optimization: {
        splitChunks: {
            chunks: 'async',
            minSize: 30000,
            maxSize: 0,
            minChunks: 1,
            maxAsyncRequests: 5,
            maxInitialRequests: 3,
            automaticNameDelimiter: '~',
            name: true,
            cacheGroups: {
                vendors: {
                    test: /[\\/]node_modules[\\/]/,
                    priority: -10
                }
            }
        }
    }
};
  • 案例一:利⽤ SplitChunksPlugin 分离基础包
    test: 匹配出需要分离的包
module.exports = {
    optimization: {
        splitChunks: {
            cacheGroups: {
                commons: {
                test: /(react|react-dom)/,
                name: 'vendors',
                chunks: 'all'
                } 
            } 
        } 
    }
};
  • 案例二: 利⽤ SplitChunksPlugin 分离页面公共文件

    minChunks: 设置最小引用次数为2次
    minuSize: 分离的包体积的大小

module.exports = {
    optimization: {
        splitChunks: {
            minSize: 0,
            cacheGroups: {
                commons: {
                    name: 'commons',
                    chunks: 'all',
                    minChunks: 2
                } 
            } 
        }
    }
};

demo09:代码分割(CodeSplitting)

广义上说,设置多入口也属于代码分割的范畴,但这里我们特指代码的动态拆分,及动态加载。

对于大型web应用尤其是单页面应用而言,按需加载/异步加载是非常必要的。有些代码往往只在特定场景下才会被使用到,将其分离出来按需进行加载,无疑可以有效减少主文件的大小。
代码分割的具体做法是定义一个分离点,在分离点中依赖的模块会被打包到一起,可以异步加载。一个分离点会产生一个打包文件。

适用的场景:

  • 抽离相同代码到一个共享块
  • 脚本懒加载,使得初始下载的代码更小

懒加载JS脚本的方式:

  • CommonJS: require.ensure
  • ES6: 动态import(目前还没有原生支持,需要babel转换)
    在分别介绍这两种方式之前,我们首先修改webpack.config.js
entry: './src/index.js',
output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js',
    chunkFilename: '[name].bundle.js'  // 代码分割生成的代码块
}

这里我们增加了chunkFilename配置,它决定非入口chunk的名称。

1.import(官方推荐方式)
src/component/App.js

...
onClick() {
    let self = this;
    // 模块懒加载
    import(/* webpackChunkName: "detail" */ './Detail')
    .then(Detail => {
      ReactDOM.render(<Detail />, self.refs['intro']);
    })
  }
...

上述代码中,我们在组件内部的click事件回调中,动态加载Detail组件并在加载完成后插入当前组件。显然的,/* webpackChunkName: "detail" */语句用来指定该分离点生成代码块的文件名。

如何使用动态import?

使用import方式需要注意的是,ES6 moduleimport/export语句)是静态的,无法用于按需加载,dynamic import仍处于提案阶段,因此必须额外安装babelSyntax Dynamic Import插件: npm install @babel/plugin-syntax-dynamic-import --save-dev

否则,你会看到这样的错误 SyntaxError: ‘import’ and ‘export’ may only appear at the top level

.babelrc文件中引入该plugins

"plugins": ["@babel/plugin-syntax-dynamic-import"],
...
}

关于ES6 module的静态加载,可以参考阮一峰的ES6教程

2.require.ensure

require.ensure(['./Detail'], function (require) {
    var Detail = require('./Detail');
    ReactDOM.render(<Detail />, self.refs['intro']);
}, 'detail');

需要注意,require.ensure会保证所有的dependencies项加载完毕后,再执行回调

demo10:数据mock

对于前后端分离开发来说,数据mock是必不可少的一环。通常我们会在本地运行一个mock server提供数据源,这时可以利用devServerproxy设置将请求代理到mock server

devServer: {
    proxy: {
        "/api": "http://localhost:8080"
    },
    ...
}

demo11:uglify和treeshaking和scope hoisting

Tree-shaking原理
利用ES6模块的特点:

  • 只能作为模块顶层的语句出现
  • import的模块名只能是字符串常量
  • import bingingimmutable

代码擦除:uglify阶段删除无用代码
使用:

mode: 'production', // 默认开启tree shaking

scope hoisting原理

  • 原理: 将所有模块的代码按照引用顺序放在一个函数作用域里,然后适当的重命名一些变量以防止变量名冲突
  • 对⽐: 通过 scope hoisting 可以减少函数声明代码和内存开销
    scope hoisting使用
  • webpack modeproduction 默认开启 (webpack4)
  • 必须是 ES6 语法,CJS 不⽀持
    webpack3需加一行代码
plugins: [
    new webpack.optimize.ModuleConcatenationPlugin()
]

demo12: webpack与ESLint集成

https://github.com/airbnb/javascript/tree/master/packages/eslint-config-airbnb

安装命令 eslint-config-airbnb 和 eslint-loader

npm i eslint-config-airbnb -D
npm i eslint eslint-plugin-import eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-jsx-a11y -D

npm i eslint-loader -D
npm i babel-eslint -D

22年可以用eslint-webpack-plugin代替

demo13: 写一个loader

// todo

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值