webpack入门与进阶:进阶用法(三)

自动清理构建目录

避免构建前每次都需要手动删除dist,可以通过clean-webpack-plugin自动清理。 webpack4.0配置

npm install clean-webpack-plugin -D
复制代码

webpack.prod.js

// webpack.prod.js
// 自动清理构建目录
 const CleanWebpackPlugin = require('clean-webpack-plugin');

module.exports = {
    ...
    ...
    plugins: [
        new CleanWebpackPlugin(),
    ]
}
复制代码

webpack5.0配置, 无需下载插件官方文档

output: {
    path: path.resolve(__dirname, 'dist'),
    // path: path.join(__dirname, 'dist'),
    // 使用 [name]占位符  设置文件指纹
    filename: '[name]_[chunkhash:8].js',
    // webpack5.0 清理构建目录配置
    clean: true,
 },
复制代码

自动添加CSS3前缀

不同浏览器,内核不一样,css3需要补全不同的前缀,实现兼容问题。 image.png

使用PostCSS插件:autoprefixer自动补齐CSS3前缀

autoprefixer是css的后置处理器,而less或者sass是css预处理器。预处理器是打包前去处理,而autoprefixer是代码打包生成之后,才开始处理。
可以在caniuse查看样式兼容问题。
通常autoprefixer会跟postcss配合使用。

npm install --save-dev postcss-loader postcss配合使用。 autoprefixer
复制代码

postcss-loader 执行顺序必须保证在 css-loader 之前,建议还是放在 less或者 sass 等预处理器之后更好。即 loader 顺序:
less-loader -> postcss-loader -> css-loader -> style-loader 或者 MiniCssExtractPlugin.loader

webpack.prod.js

{
test: /\.less$/,
use: [
  // 'style-loader',
  MiniCssExtractPlugin.loader,
  'css-loader',
  'less-loader',
  { loader: "postcss-loader" }
]
},
复制代码

根目录新建postcss.config.js

module.exports = {
  plugins: [
    require('autoprefixer')()
  ]
}
复制代码

package.json

"browserslist": [
    "defaults",
    "not ie < 11",
    "last 2 version",
    "> 1%",
    "ios 7",
    "last 3 ios version"
  ]
复制代码

解决webpack autoprefixer配置不生效

移动端CSS px 自动转换成rem

为适配不同的机型,实现响应式布局,可使用rem作为尺寸单位。
使用 px2rem-loader。
页面渲染是计算根元素的font-size。lib-flexible库
现在大部分使用viewport去兼容不同的浏览器。
viewport 待更新...

资源内联

资源内联的意义
代码层面:

  • 页面框架的初始化脚本
  • 上报相关打点
  • css内联避免页面闪动

请求层面:减少HTTP网络请求数

  • 小图片或者字体内联(url-loader)

HTML和JS内联

raw-loader内联html,js

CSS内联

  • 方案一:借助style-loader 把 CSS 插入到 DOM 中。
  • 方案二:html-inline-css-webpack-plugin

多页面应用

每⼀次⻚⾯跳转的时候,后台服务器都会给返回⼀个新的 html ⽂档,这种类型的⽹站也就是多⻚⽹站,也叫做多⻚应⽤。
动态获取entry和设置html-webpack-plugin数量
利用glob.sync,以同步的方式查询文件。
glob.sync(path.join(__dirname, './src/*/index.js'));查询根目录下的文件夹下的所有index.js文件

npm i glob -D
复制代码

webpack.prod.js

const glob = require('glob');


const setMPA = () => {
  const entry = {};
  const htmlWebpackPlugins = [];
  // 查找出这个项目下的所有index.js文件
  const entryFiles = glob.sync(path.join(__dirname, './src/*/index.js'));
  // console.log(entryFiles, 'entryFiles')
  // [ '/Users/**/program/webpack-demo/src/index/index.js','/Users/**/program/webpack-demo/src/search/index.js' ]
  Object.keys(entryFiles).map(index => {
    const entryFile = entryFiles[index];
    const match = entryFile.match(/src\/(.*)\/index\.js/);
    // 对应入口文件夹名称
    const pageName = match && match[1];
    entry[pageName] = entryFile;
    htmlWebpackPlugins.push(
      new HtmlWebpackPlugin({
        // 模板所在位置
        template: path.join(__dirname, `src/${pageName}/index.html`),
        // 指定打包后的文件名称
        filename: `${pageName}.html`,
        // 指定生成的html需要哪些chunk
        chunks: [pageName],
        inject: true,
        minify: {
          html5: true,
          collapseWhitespace: true,
          preserveLineBreaks: false,
          minifyCSS: true,
          minifyJS: true,
          removeComments: false
        }
      })
    );
  })

  return {
    entry,
    htmlWebpackPlugins
  }
}

const { entry, htmlWebpackPlugins } = setMPA();

module.exports = {
    entry,
    ...
    plugins: [
        ...htmlWebpackPlugins,
        // 压缩css
        new MiniCssExtractPlugin({
          filename: '[name]_[contenthash:8].css'
        }),
        new OptimizeCSSAssetsPlugin(),
    ]
}

复制代码

Source map

作⽤:通过 source map 定位到源代码。
简单说,Source map就是一个信息文件,里面储存着位置信息。也就是说,转换后的代码的每一个位置,所对应的转换前的位置。
基本上开发环境直接用source-map。
production环境就把source-map添加到Error Reporting Tool(e.g. Sentry)上。这样既不直接暴露源代码,也能方便解决production环境遇到的bug。
source map科普⽂:www.ruanyifeng.com/blog/2013/0… \

source-map关键字

  • eval:使用eval包裹模块代码
  • source-map:产生.map文件
  • cheap 不包含列信息
  • inline 将.map作为DataURI嵌入,不单独生成.map文件
  • module: 包含loader的sourcemap

souce-map类型 image.png

devtool配置对应打包后的文件差异

// webpack.prod.js
module.exports = {
    ...
    mode: 'none',
    devtool: 'eval'
}
复制代码

1、设置为eval,打包后,会有eval包裹

eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _helloWorld__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);\n\nsetTimeout(function () {\n  document.write((0,_helloWorld__WEBPACK_IMPORTED_MODULE_0__.helloWorld)());\n});\n\n//# sourceURL=webpack://webpack-demo/./src/index/index.js?");
复制代码

2、设置为source-map
js会和map内容进行分离 image.png 3、设置为inline-source-map
js会和map内容不会进行分离

基础库分离

如果需要引用一个库,但是又不想让webpack打包(减少打包的时间),并且又不影响我们在程序中以CMD、AMD或者window/global全局等方式进行使用(一般都以import方式引用使用),那就可以通过配置externals。
这样做的目的就是将不怎么需要更新的第三方库脱离webpack打包,不被打入bundle中,从而减少打包时间,但又不影响运用第三方库的方式,例如import方式等。

方式一:使用html-webpack-externals-plugin

  • 思路:将react、react-dom基础包通过cdn引入,不打入bundle中
  • 方法:使用html-webpack-externals-plugin
npm install --save-dev html-webpack-externals-plugin
// npm@6+
npm install html-webpack-externals-plugin -D --legacy-peer-deps
复制代码

1、配置webpack.prod.js

// 分离基础库
const HtmlWebpackExternalsPlugin = require('html-webpack-externals-plugin');


module.export = {
    ...
    plugins: [
        ...htmlWebpackPlugins,
    // 压缩css
    new MiniCssExtractPlugin({
      filename: '[name]_[contenthash:8].css'
    }),
    new OptimizeCSSAssetsPlugin(),
    new HtmlWebpackExternalsPlugin({
      externals: [
            {
              module: 'react',
              entry: 'https://11.url.cn/now/lib/16.2.0/react.min.js',
              global: 'React',
            },
            {
              module: 'react-dom',
              entry: 'https://11.url.cn/now/lib/16.2.0/react-dom.min.js',
              global: 'ReactDOM',
            },
          ]
       }),
    ]
}
复制代码

2、在src/search/index.html页面引入react react-dom包

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>

<body>
  <div id="root"></div>
  // 引入react react-dom包
  <script type="text/javascript" src="https://unpkg.com/react@17/umd/react.production.min.js"></script>
  <script type="text/javascript" src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>
</body>

</html>
复制代码

分离react react-dom包之后,打包之后的体积大大缩小 image.png

关于安装可能出现的问题:
如果[email protected]+,下载会报错,错误如下:

image.png 错误解决方案:
gitee.com/vincentyun/…
www.it1352.com/2315867.htm…
找了好久才解决,原来因为npm7.x对某些事情比npm6.x更严格。
通常,最简单的解决方法是将--legacy-peer-deps标志传递给 npm ( npm install --legacy-peer-deps ),或者使用npm@6。
如果这不能立即起作用,也许可以先删除node_modules和package-lock.json。它们将被重新创建。
提示:使用npm@6不需要卸载npm@7。使用npx指定npm的版本。例如:npx -p npm@6 npm i --legacy-peer-deps。

image.png

方式二:利用splitChunks进行公共脚本分离(去掉html-webpack-externals-plugin相关配置)

从 webpack v4 开始内置了SplitChunksPlugin,直接通过optimization.splitChunks配置 webpack.prod.js

const setMPA = () => {
    ...
    htmlWebpackPlugins.push(
        new HtmlWebpackPlugin({
            chunks: ['vendors', pageName],//此处多配置一个vendors,与cacheGroups中的name对应
        })
    )
}

module.exports = {
    ...
    optimization: {
    // splitChunks分离基础包
    splitChunks: {
      cacheGroups: {
        commons: {
          test: /(react|react-dom)/,
          // vendors需要添加到htmlWebpackPlugins的chunk里
          name: 'vendors',
          chunks: 'all'
        }
      }
    },
  },
  // source-map,产生.map文件
  devtool: 'inline-source-map',
}
复制代码

image.png 执行npm run build编译打包之后,在html文件里,会引入这个vendors

image.png image.png

tree shaking(摇树优化)

1、概念
1个模块可能有多个⽅法,只要其中的某个⽅法使⽤到了,则整个⽂件都会被打到bundle ⾥⾯去,tree shaking 就是只把⽤到的⽅法打⼊ bundle ,没⽤到的⽅法会在uglify 阶段被擦除掉。
2、使用
webpack 默认⽀持,在 .babelrc ⾥设置 modules: false 即可
production mode的情况下默认开启
3、要求
必须是ES6的语法,CJS的方式不支持
4、DCE (Dead code elimination)

  • 代码不会被执行,不可到达
  • 代码执行的结果不会被用到
  • 代码只会影响死变量(只写不读)
if(false){
    console.log('这段代码永远不会被执行')
}
复制代码

原理

利⽤ ES6 模块的特点:

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

代码擦除:uglify 阶段删除⽆⽤代码
(不懂......)

代码分割和动态import

一、代码分割

1、意义

对于⼤的 Web 应⽤来讲,将所有的代码都放在⼀个⽂件中显然是不够有效的,特别是当你的某些代码块是在某些特殊的时候才会被使⽤到。webpack 有⼀个功能就是将你的代码库分割成chunks(语块),当代码运⾏到需要它们的时候再进⾏加载。

2、适用场景

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

二、动态import

懒加载JS脚本的方式:

  • commonJS:require.ensure
  • es6:动态import(目前还没有原生支持,需要babel转换)

1、安装插件
npm install @babel/plugin-syntax-dynamic-import --save-dev
2、在.babelrc文件里加入这个插件

{
  "presets": [
    "@babel/preset-env",
    "@babel/preset-react"
  ],
  "plugins": [
    "@babel/plugin-syntax-dynamic-import"
  ]
}
复制代码

3、新建text.js

import React from 'react';

export default () => <div>动态 import</div>
复制代码

4、在src/search/index.js文件里引入

'use strict';
import React from 'react';
import ReactDom from 'react-dom';
import './search.less'
import imgSrc from './images/2.png'

class Search extends React.Component{
  constructor() {
    super()
    this.state = {
      Text: null
    }
  }
  render() {
    // debugger
    const { Text } = this.state;
    return <div className="search-text">
      <div>Search Text111</div>
      <img className="img" src={imgSrc} onClick={this.loadComponent.bind(this)}></img>
      {/* 渲染页面 */}
      {
        Text ? <Text/> : null
      }
    </div>
  }
  loadComponent() {
    // 动态引入文件
    import('./text.js').then((Text) => {
      this.setState({
        Text: Text.default,
      })
    })
  }
}

ReactDom.render(
  <Search/>,
  document.getElementById('root')
)
复制代码

5、执行npm run build,打包完成之后会多一个数字开头的js文件,就是动态引入的文件。开头的数字就是懒加载的id。

image.png

点击图片,就会加载js

image.png

在webpack中使用ESLint

⾏业⾥⾯优秀的 ESLint 规范实践
腾讯:

一、webpack与CI/CD集成

(loading......)

二、webpack与ESLint集成

1、在react中集成 1.1 安装

npm install --save-dev eslint @babel/eslint-parser @babel/preset-react@latest eslint-plugin-react eslint-config-alloy
复制代码
npm install eslint-loader babel-eslint -D
复制代码
npm install eslint-config-airbnb -D
复制代码

1.2webpack.prod.js中加入eslint-loader

module.exports = {
    module: {
        rules: [
            {
                test: /\.js$/,
                // 添加eslint-loader
                use: ['babel-loader', 'eslint-loader'],
             },
        ]
    }
}
复制代码

1.3 配置.eslintrc.js
eslint官网

module.exports = {
  parser: 'babel-eslint',
  // 继承
  extends: ['airbnb'],
  // 环境变量
  env: {
    browser: true,
    node: true,
  },
};
复制代码

在webpack中打包组件和基础库

webpack 除了可以⽤来打包应⽤,也可以⽤来打包 js 库,实现⼀个⼤整数加法库的打包。

  • 需要打包压缩版和⾮压缩版本
  • 打包好的组件和基础库,⽀持 AMD/CommonJS/ESModule 模块引⼊,也支持直接通过script引入

如何将库暴露出去?

  • library:指定库的全局变量
  • libraryTarget:支持库引入的方式

创建一个简单的组件库

1、新建一个项目larger-num,初始化该项目

npm init -y
复制代码
npm install webpack webpack-cli
复制代码

在根目录下新建webpack.config.jssrc/index.js

image.png 2、src/index.js

导出一个方法

export default function add(a,b) {
  let i = a.length - 1;
  let j = b.length - 1;
  // 进位
  let carry = 0;
  let ret = "";
  while(i >= 0 || j >= 0) {
    let x = 0,y = 0, sum;
    if (i >= 0) {
      x = a[i] - '0';
      i--;
    }
    if (j >= 0) {
      y = b[j] - '0';
      j--;
    }
    sum = x + y + carry;
    if (sum >= 10) {
      carry = 1;
      sum -= 10;
    } else {
      carry = 0;
    }
    // 0+''
    ret = sum + ret;
  }
  if (carry) {
    ret = carry + ret;
  }
  return ret;
}

// add('999','1')
// add('1','999')
// add('123','2123')
复制代码

3、配置打包信息webpack.config.js webpack.docschina.org/configurati…

module.exports = {
  entry: {
    'large-number': './src/index.js',
    'large-number.min': './src/index.js',
  },
  // 导出一个库
  output: {
    filename: '[name].js',
    library: {
      // 配置库的名字
      name: "largeNumber",
      // 配置将库暴露的方式
      type: "umd",
      export: "default"
    },
    // library: 'largeNumber',
    // libraryTarget: 'umd',
  },
  mode: "production",
}
复制代码

4、打包

添加一个script

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack"
  },
复制代码

执行打包命令

npm run build
复制代码

打包结果 image.png

image.png

只打.min文件压缩

webpack.docschina.org/plugins/ter…

1、下载terser-webpack-plugin

npm install terser-webpack-plugin --save-dev
复制代码

2、配置webpack.config.js

const TerserPlugin = require("terser-webpack-plugin");
module.exports = {
  ...
  ...
  mode: "none",
  optimization: {
    minimize: true,
    minimizer: [
      // 通过 include 设置只压缩 min.js 结尾的⽂件
      new TerserPlugin({
        include: /\.min\.js$/,
      })
    ]
  },
}
复制代码

3、执行npm run build,可以看到文件大小有明显变化,dist目录下的生成了一个压缩的一个未压缩的文件

image.png

image.png

4、发布到npm

如何发布npm包

webpack实现SSR打包

页面打开过程(客户端渲染)

客户端渲染,页面整体的打包过程如下:

用户点击一个按钮,会加载一个新的webview,加载完新的webview之后,开始加载新的页面

  • 页面开始加载,此时会出现一段时间的白屏时间,页面没有内容(因为还没加载html)
  • HTML加载成功,开始加载数据
    • 此时会有loading图标等等,告诉用户页面正在加载
    • 浏览器开始请求CSS、JS
    • 解析和执行CSS,页面上就会出现一些样式
    • 解析和执行JS,会执行JS的一些逻辑,比如请求图片资源,请求数据等等
  • 数据加载成功,渲染成功开始,加载图片资源
  • 图片加载成功,页面可交互

缺点:整个过程是串行过程,白屏时间长

服务端渲染(SSR)是什么

渲染: HTML + CSS + JS + Data -> 渲染后的 HTML

服务端渲染:

  • 所有模板等资源都存储在服务端,可将多个请求数量,优化成一个,是一个并行的加载
  • 客户端渲染是依赖用户的网络,而服务端渲染是利用内⽹机器拉取数据,加载速度更快
  • ⼀个 HTML 返回所有数据

优势:

  • 减少白屏时间
  • 对于SEO友好

浏览器和服务器交互过程

image.png

具体过程:

  • 页面请求开始,请求会到达服务端(server)
  • 服务端(server)会拿到HTML模板(HTML templete)和页面数据(data),渲染引擎会将HTML templete和data会进行server render
  • server render之后,浏览器会解析并渲染拼装好的HTML文件,此时,用户可以看到页面
  • 再加载并执行JS文件和其他资源文件,页面到达完全可交互的状态

客户端渲染 VS 服务端渲染

image.png

总结:

  • 客服端渲染是一个前端的JS渲染,而服务端渲染是在node端等底层语言里进行的渲染
  • 服务端渲染 (SSR) 的核⼼是减少请求

Guess you like

Origin juejin.im/post/7031448248573231134