阅读 85

模块化之webpack

偶然的机会,读到苏轼了《赤壁赋》。和高中读来完全不一样,也许是上了年纪,饱尝了几年打工人的辛酸,忽然折服于苏轼的豁达,有种醍醐灌顶的舒畅,粘贴一些名句,与君共勉。

逝者如斯,而未尝往也;盈虚者如彼,而卒莫消长也。盖将自其变者而观之,则天地曾不能以一瞬;自其不变者而观之,则物与我皆无尽也,而又何羡乎!且夫天地之间,物各有主,苟非吾之所有,虽一毫而莫取。惟江上之清风,与山间之明月,耳得之而为声,目遇之而成色,取之无禁,用之不竭。是造物者之无尽藏也,而吾与子之所共适。 ----- 《赤壁赋》

-------------------------------------我是一条分割线--------------------------------------

模块化

  • 提高工作效率,降低维护成本

一、模块化演变

  • 文件划分方式(script中全局引入)
    • 污染全局作用局
    • 命名冲突
    • 模块之间的依赖关系无法解决
  • 命名空间方式(全局引入不同的命名空间)
  • IIFE(立即执行函数)
    • 确保私有空间的安全

二、模块化规范

CommonJS规范以同步模式加载模块()

  • 一个文件就是一个模块
  • 每个模块都有单独的作用域
  • 通过modele.exports导出成员
  • 通过require函数载入模块

AMD(异步模块定义规范)--Require.js node中遵循CommonJS,浏览器中遵循ES Modules规范

三、ES Modules(最主流的前端规范标准)

通过给script标签添加 type = model 的属性,就可以以ES Module的标准执行其中的JS代码

1、基本特性

  • ESM自动采用严格模式,忽略‘use strict’
  • 每个ES Module都运行在自己的私有作用域中
  • ESM是通过CORS的方式进行外部JS请求(需要请求的环境支持跨域)
  • ESM的script的标签会延迟执行脚本

2、ES Module的导入和导出

//index.js  导出
var name = '11
export {name}
export default {name}

//导入
import {name} from "index.js"
console.log(name)  //'11'


import {default as data} from "index.js"
console.log(data.name)
复制代码
  • export 后接{}导出为固定的语法,并不是指导出一个对象,引用的时候指向的是内存空间的地址
  • 导出的是只读对象 ,不能修改

3、ES Modules in Node.js

  • ES Module中可以导入CommonJS模块
  • CommonJS不能导入ES Module模块
  • CommonJS始终只会导出一个默认成员
  • 注意import不是解构导出对象,
//module.mjs
var foo = '2',
  bar = '55'
export { foo, bar }
//-----------------------------------------------------------------------------------------


//common.js,始终只会导出一个默认的成员
module.exports = {
  foo: 'common js',
}
exports.foo = 'commonjs2'     exports是module.exports的一个别名
//---------------------------------------------------------------------------------------



//index.mjs 

import { foo, bar } from './module.mjs'
console.log(foo, bar)
import fs from 'fs'
fs.writeFileSync('./foo,txt', 'es modlue working')
import { writeFileSync } from 'fs'
writeFileSync('./bar.txt', '111222')

//在esm中引入commonJS模块
import mod from './common.js'
console.log(mod)

复制代码

4、ES Modlue 与CommonJS的差异

  • ESM中没有CommonJS中的那些模块成员了(require、module、exports、__filename、__dirname)
  • 在package.json中添加 type:'modlue' 属性,就可以在js文件中直接使用ESM,而不是要在mjs文件中。此时CommonJS文件需要更名为 .cjs 文件
  • 运行ESM文件node --experimental-modules index.js
//ESM中的文件路径
import {fileURLToPath} from 'url'
import {dirname, firname} from 'path'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
复制代码

四、Webpack打包

1、Loader

默认入口文件为:src/index.js,输入路径为dist/main.js

Loader是webpack的核心特质,借助于Loader就可以加载任何类型的资源

loader负责资源文件从输入到输出的转换,同一个资源文件可以依次依赖多个Loader

因为模块打包的需要,所以处理import和export,并不能去转换代码中的其他es6特性。 webpack加载器分类

  • 编译转换类
  • 文件操作类
  • 代码检查类
//webpack.config.js

const path = require('path')

module.exports = {
  //yarn webpack --mode development   以开发模式运行
  //yarn webpack --mode none   以最原始的状态打包带包
  mode: 'none',
  entry: './src/main.css', //自定义入口文件
  output: {
    filename: 'index.js', //输出的文件名称
    path: path.join(__dirname, 'dist'), //输出的文件夹名称,路径为绝对路径
  },
  //yarn add css-loader --dev  处理css模块
  //yarn add style-loader --dev   将css-loader处理后的结果以style标签的方式追加到页面上
  module: {
    rules: [
      {
        test: /.css$/,
        use: ['style-loader', 'css-loader'], //当配置了多个loader,执行的时候是从后往前执行
      },
      //yarn add file-loader --dev   文件加载器
      //yarn add url-loader --dev   可以将图片转为base64
      //小文件使用Data URLs,减少请求的次数
      //大文件单独提取存放,提高加载速度
      {
        test: /.jpg$/,
        // use: 'file-loader',

        use: {
          loader: 'url-loader',
          Options: {
            limit: 10 * 1024, //10kb以下的会处理为base64
          },
        },
      },
    ],
  },
}

复制代码

yarn add babel-loader @babel/core @babel/preset-env --dev

webpack只是打包工具

加载器可以用来编译转化代码

module: {
    rules: [
      {
        test: /.js$/,
        use: {
          //代替默认的加载器,可以处理js的新特性
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env'],
          },
        },
      },
   ]
}

复制代码

webpack模块加载的方式

  • 遵循ES Module标准的import声明
  • 遵循CommonJS标准的require函数
  • 遵循AMD标准的define函数和require函数
  • 样式代码中的@import指令和url函数
  • HTML代码中图片标签的src属性

2、Plugin

自动清除输出目录

yarn add clean-webpack-plugin html-webpack-plugin copy-webpack-plugin --dev

const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
module.exports = {
  mode: 'none',
  entry: './src/index.js',
  output: {
    filename: 'index.js',
    path: path.join(__dirname, 'dist'),
    publicPath: 'dist/',
  },
  module: {
    rules: [
      {
        test: /.js$/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env'],
          },
        },
      },
      {
        test: /.css$/,
        use: ['style-loader', 'css-loader'], //当配置了多个loader,执行的时候是从后往前执行
      },
      {
        test: /.jpg$/,
        use: {
          loader: 'url-loader',
          options: {
            limit: 10 * 1024,
          },
        },
      },
      {
        test: /.html$/,
        use: {
          loader: 'html-loader',
          options: {
            attrs: ['img:src', 'a:href'],
          },
        },
      },
    ],
  },
  //c插件
  plugins: [
      new CleanWebpackPlugin(),
       //用户生成 index.html文件
        new HtmlWebpackPlugin({
          title: 'Webpack Plugin Sample',
          meta: {
            viewport: 'width=device-width',
          },
          template: './src/index.html',
        }),
        //about.html
        new HtmlWebpackPlugin({
          filename: 'about.html', //指定输出的文件名
          template: './src/index.html',
        }),
        //打包的时候将public目录下文件直接拷贝
       new CopyWebpackPlugin({
          patterns: [
            {
              from: path.join(__dirname, 'public'),//需要复制的文件夹
              to: 'static',//复制到dist下的文件的名称
            },
          ],
        }),
    ],
}
复制代码

3、插件的开发

plugin必须是一个函数或者是一个包含apply方法的对象 通过在声明周期的狗子中挂载函数实现扩展

//定义一个删除bundle.js的注释
class MyPlugin {
  apply(compiler) {
    console.log('MyPlugin 启动')
    compiler.hooks.emit.tap('MyPlugin', (compiliation) => {
      //compiliation =>可以理解为此次打包的上下文
      console.log()
      for (const name in compiliation.assets) {
        // console.log(name)
        // console.log(compiliation.assets[name].source())
        if (name.endsWith('.js')) {
          const contents = compiliation.assets[name].source()
          const withoutComments = contents.replace(/\/\*\*+\*\//g, '')
          compiliation.assets[name] = {
            source: () => withoutComments,
            size: () => withoutComments.length,
          }
        }
      }
    })
  }
}

复制代码

4、增强webpack的开发体验

watch工作模式:yarn webpack --watch 浏览器自动刷新:brower-sync

5、Webpack Dev Server

  • 集成自动编译自动刷新浏览器等功能
  • 静态资源需要额外配置contentBase
  • 设置代理API解决跨域问题
devServer: {
    contentBase: './public',
    proxy: {
      '/api': {
        //http://localhost:8080/api/users  => https://api.github.com/api/users
        target: 'https://api.github.com',
        //http://localhost:8080/api/users  => https://api.github.com/users
        pathRewrite: {
          '^/api': '',
        },
        //不能使用locahost:8080作为请求Github的主机名
        changeOrigin: true,
      },
    },
  }
复制代码

6、SourceMap(源代码地图)

解决了源代码与运行代码不一致所产生的问题

7、devtool模式对比

  • eval-是否使用eval执行模块代码
  • cheap-Source Map是否包含行信息
  • module-是否能够得到Loader处理前的源代码

先择合适的Source Map

  • 开发模式:cheap-module-eval-source-map
    • 每行代码不超过80个字符
    • 经过Loader转换过后的代码的差异较大
    • 首次打包速度慢一些,重写打包速度较快
  • 生产环境:none(nosources-source-map)
    • Source Map会暴露源代码
    • 调试是开发阶段的事情

7、模块热更新(HMR)

webpack在页面不刷新的情况下及时更新代码块 HMR是webpack中最强大的功能之一

  • Webpack中的HMR需要手动处理模块热替换逻辑
  • 样式文件可以开箱即用,因为样式文件是经过Loader处理的
  • 通过脚手架创建的项目内部都集成了HMR方案
//webpack.config.js
const webpack = require('webpack')
devServer: {
  hot: true,
},
plugins: [
  new webpack.HotModuleReplacementPlugin(),
],


//手动设置js热替换
//main.js  ./heading需要监听的模块
module.hot.accept('./heading', () => {
  console.log('heading模块更新了,需要收手动处理替换热更新逻辑')
})

复制代码

8、生产环境优化

  • 配置文件根据环境不同导出不同的配置
  • 一个环境对应一个配置文件
//webpack.config.js
module.exports = (env, argv) => {
  const config = {
    mode: 'development',
    entry: './src/main.js',
    output: {
      filename: 'js/bundle.js',
    },
    devtool: 'cheap-eval-module-source-map',
    devServer: {
      hot: true,
      contentBase: 'public',
    },
    module: {
      rules: [
        {
          test: /\.css$/,
          use: ['style-loader', 'css-loader'],
        },
        {
          tets: /\.(png|jpe?g|gif)$/,
          use: {
            loader: 'file-loader',
            options: {
              outputPath: 'img',
              name: '[name].[ext]',
            },
          },
        },
      ],
    },
    plugins: [
      new HtmlWebpackPlugin({
        title: 'Webpack Tutorial',
        template: './src/inedx.html',
      }),
      new webpack.HotModuleReplacementPlugin(),
    ],
  }
  if (env === 'production') {
    config.mode = 'production'
    config.devtool = false
    config.plugins = {
        ...config.plugins,
        new CleanWebpackPlugin(),
        new CopyWebpackPlugin(['public'])
    }
  }
  return config
}

复制代码
yarn webpack --dev production
复制代码

9、不通环境对应不同配置文件

//webpack.prod.js

//执行  yarn webpack --config webpack.prod.js
//或者将命令添加到scripts


const common = reuqire('./webpack.common.js') //公共配置
const merge = require('webpack-merge')
const { CleanWebpackPlugin } = requre('clean-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
module.exports = merge(common, {
  mode: 'production',
  plugins: [new CleanWebpackPlugin(), new CopyWebpackPlugin(['public'])],
})

复制代码

10、Tree-Shaking

  • 会自动移除掉代码中未被引用的代码,会在生产模式下自动启用。
  • Tree Shaking要使用ES Module
  • babel的某些功能会将ES Module转换为CommonJS,此时Tree Shaking就会失效
//手动开启
optimization:{
  usedExports:true,//指导出被引用部分的代码
  concatenateModules:true,//尽可能把所有模块合并到一个函数中导出,提升运行效率,减少代码体积
  minimize:true //压缩结果输出
},
复制代码

11、多入口打包

entrry:{
  index:'./src/main.js',
  album:'./src/ablum.js'
},
optimization:{
    splitChunks:{
        chunks:'all'//将所有的公共模块提取到单独的bundle中
    }
},
plugins: [
      new HtmlWebpackPlugin({
        title: 'Multi Entry',
        template: './src/inedx.html',
        filname:'index.html',
        chunks:['index']
      }),
      new HtmlWebpackPlugin({
        title: 'Multi Entry',
        template: './src/album.html',
        filname:'album.html',
        chunks:['album']
      }),
   ]

复制代码

12、hash值

  • hash:整个项目级别的(每个文件都有相同的hash值)
  • chunkhash:某路入口文件的hash值全部发生变换(每路chunk有相同的hash值)
  • contenthash:某个文件发生变化,打包后的hash值才会发生变化

文章分类
前端
文章标签