webpack理解与实践

333 阅读5分钟
  1. webpack有哪些能力?

    提到webpack,提到webpack能干什么?

    初学者都能下意识地都能回答出:webpack是一个打包工具。

      这么说没什么不对,但是失于片面。webpack的目的是为了构建出一个高质量的web应用,于此同时,需要提供一整套的本地开发流程,可以让开发者更愉快地开发出所需的功能。

    • 前者对应的是webpack的打包能力,因为浏览器只认识js, css, html,webpack把开发中用到的各种文件都集成起来,使其能在页面中被浏览。
    • 后者对应的是开发者体验,诸如本地服务预览,热更新,wtach模式,sourceMap等等,让开发者获得更好地开发体验。

    1.   打包工具

      前端开发工作可以粗略地分为两类,一类是应用开发,一类是包的开发,二者都会用到打包,不过webpack更多用于网页应用的开发(npm包开发打包用rollup较好)。

    为什么开发时为什么需要打包?

    1. 将代码模块化,可以避免作用域污染等问题,降低开发者的心智负担,更符合分治的开发原则。开发时可以直接使用import这种模块化语句,直接简单。

    2. 将多个文件合成为一个文件可以降低网络请求的次数,加快网页加载速度。

    3. 使用多种类型的文件,然后打包到一起,用浏览器能识别的形式展示(主要是js)。

    打包的产物是什么?

      webpack开发应用时产物是iife(立即执行函数)形式的,简单情况下,会打包出一个bundle.js这样的文件,然后在一个html文件中进行引用。当然webpack也可以打包es,cjs等格式的产物,不过是在打包npm包的时候。

    1.   开发工具

      webpack的目的和产物是得到可以部署的打包后代码,通常这些代码主体部分就是纯js文件,但是在开发中我们会使用到很多“方言”,如ts,React 的jsx语法,less 或 scss类型的css语言,这些内容需要用编译工具编译成纯js语言,这些编译工具都可以通过loader工具集成在webpack之中。这是打包工具的内容,同样也是开发工具在webpack中的表现。

      除此之外以下列出webpack提供的便捷开发工具能力:

    • 本地服务器预览:webpack-dev-server提供本地服务器预览开发内容,同时可以做一些网址的代理。

    • sourceMap:开发出的内容一般都经过压缩,代码变得不易阅读,开发时需将实际源码与运行代码通过map文件关联起来,方便定位问题。

    • watch模式:类似于nodemon,当文件发生变化,node重新编译代码,预览页面刷新。实际上devServer也能提供这种服务。

    • 热更新(hrm):如果代码每更新一次代码就重编译太慢了,热更新可以不刷新页面,只改变代码变更影响的部分内容,实时显示变更,避免了在开发弹窗等内容时,每刷新一次都得重新点开弹窗的尴尬。

    1.   优化工具

      所谓web的优化狭义上来讲就是打包产物大小的优化,产物越小,页面展示得越快。

    • treeShaking:用es6的模块语法(import, export)可以触发webpack生产环境默认的treeShaking。在应用中引用npm包的最好只引用用到的部分,否则会影响对于npm包的treeShaking。

    • 路由懒加载:结合代码分块进行,react等框架也有对应的解决方案。

    • cdn代替引用:用cdn地址直接在html中引入较大的包。

    • 代码优化工具:打包时对代码进行压缩。

    • gzip优化:使用Nginx的话需要在服务端做一些配置。

  2. 学习webpack的目标

    1.   开发应用 (react + scss + ts )

      开发体验:本地服务器 & 热更新 & sourceMap。

      代码运行:loader& plugin配置。

    1.   基础代码运行(js + devServer + css | scss)

      首先搭建一个基础的代码架子,基础的配置可以代码实时更新,并简单区分开发模式和生产模式。

      代码结构:

    webpack-case
    ├─package-lock.json
    ├─package.json
    ├─webpack.config.js
    ├─src
    |  ├─index.js
    |  └index.scss
    ├─dist
    

      简要列一下主要内容:

    • pakage.json:引入几个需要的npm包,简单区分build和start两种环境,用webpack-dev-server开启实时更新。

      • {
          "name": "webpack-case",
          "version": "1.0.0",
          "description": "",
          "main": "index.js",
          "scripts": {
            "test": "echo "Error: no test specified" && exit 1",
            "build": "NODE_ENV=production webpack",
            "start": "NODE_ENV=development webpack server --open"
          },
          "keywords": [],
          "author": "",
          "license": "ISC",
          "devDependencies": {
            "css-loader": "^6.7.3",
            "html-webpack-plugin": "^5.5.0",
            "sass": "^1.57.1",
            "sass-loader": "^13.2.0",
            "style-loader": "^3.3.1",
            "webpack": "^5.75.0",
            "webpack-cli": "^5.0.1",
            "webpack-dev-server": "^4.11.1"
          }
        }
        
    • webpack.config.js: 简单配置了文件的出口入口,还有loader和plugin

      • const path = require('path');
        const HtmlWebpackPlugin = require('html-webpack-plugin');
            
        module.exports = {
          entry: './src/index.js',
          output: {
            filename: 'bundle.js',
            path: path.resolve(__dirname, 'dist'),
            clean: true
          },
          mode: process?.env?.NODE_ENV === 'production' ? 'production':  'development',
          devServer: {
            static: './dist',
          },
          module: {
            rules: [
              {
                test: /.s?css$/i,
                use: ['style-loader', 'css-loader', 'sass-loader'],
              },
            ],
          },
          plugins: [
            new HtmlWebpackPlugin({
              title: 'webpack-case',
            }),
          ],
        }
        

        ./src/index.js

    import './index.scss';
    
    const div = document.createElement('div');
    div.className = 'text';
    div.innerHTML='webpack-case is there';
    document.body.appendChild(div);
    

          ./src/index.scss

    $color: #e0c9ba;
    
    .text { 
      color: $color;
    }
    
    1.   添加react + antd以及基于react的热更新

      参考从零配置webpack 5 + React脚手架(一) - 掘金

      react引入需要添加相应loader

    {
    test: /.(js|jsx)$/,
    exclude: /(node_modules|bower_components)/,
    use: {
      loader: 'babel-loader',
      options: {
        presets: ['@babel/preset-env', '@babel/preset-react'],
        plugins: ['@babel/plugin-transform-runtime', '@babel/plugin-proposal-class-properties'],
      },
    },
    },
          
    

      添加默认拓展名:

       resolve: {
        extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'],
      },
    

      配置react热更新:

    devServer: {
    static: './dist',
    hot: true,
    }
    
    // App.jsx
    import { hot } from 'react-hot-loader/root';
    export default hot(App);
    
    1.   添加typescript

      添加ts-loader

    {
        test: /.tsx?$/,
        use: 'ts-loader',
        exclude: /node_modules/,
    },
    

      添加react库相关typs包

        "@types/react": "^18.0.27",
        "@types/react-dom": "^18.0.10",
    

      react等包的引入方式改变:

    import * as React from "react";
    import * as ReactDOM from 'react-dom';
    
    1.   打包优化

      Cdn 引入react包:

    // webpack.config.js
      externals: {
        react: 'React',
        'react-dom': 'ReactDOM',
      } // 包名: 代码中使用的对象
    
    // public/index.html
      <script
        crossorigin
        src="https://unpkg.com/react@18/umd/react.production.min.js"
      ></script>
      <script
        crossorigin
        src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"
      ></script>
    

      treeShaking:

      使用es6语法在打包production模式下会自动优化。有些包内部可能不支持treeShaking,需要只引入特定的部分。

      懒加载 一般结合react路由使用。

    1.   css隔离

      首先引用一张网络上的css隔离方式对比图:

      由上可知,css modules是一种较好的通过工具约束来隔离css的方法,其缺点是依赖打包工具,但是我们讨论的就是如何通过打包工具实现css隔离,这一点天然成立。此外BEM和预处理器一般也都会在项目中作为一种css的默认规范被使用到。

      引入方式

    import styles from './App.scss';
    

      使用css方式

    <div className={styles.text}>hello webpack test</div>
    

      webpack, css-loader配置

      {
        test: /.s?css$/i,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              modules: {
                auto: resourcePath => resourcePath.endsWith('.scss'),
                localIdentName: '[local]_[hash:base64:10]',
              },
              importLoaders: 1,
            },
          },
          'sass-loader',
        ],
        exclude: /node_modules/,
      },
    
    1.    配置lint

具体过程参考👋 Hello ESLint | Linter上手完全指南

安装7版本的eslint:npm i -D eslint@7.19.0 最新版本可能不适用于某些老版本vscode。

使用npx eslint --init 进行快速配置

  • 添加ts相关配置在.eslintrc.js文件中
  •   parserOptions: {
        project: ["./tsconfig.json"], // 告诉 eslint:tsconfig 在哪
      }
    
  • 配置一些rules后,eslint控制台无报错就运行正常了。

    1.   开发npm包(*)

  •   目前开发npm包使用rollup来打包更好,冗余代码较少。不过webpack也可以用于打包npm包,基本上各种格式的输出都支持。