模块化开发 webpack

 

目录

一、ES Modules

1.基本特性

2.ES Module的导入导出

3.ES Modules 运行环境的兼容性问题

4.Node.js对ES Modules的支持情况

5.Node 环境中,ES Modules 载入CommonJs模块

二、Webpack

1.webpack命令

2.Webpack 配置文件

2.1 webpack.config.js

2.2 loader分类

2.3 webpack工作原理

2.4 自定义loader和plugin

2.5 webpack dev server 自动编译和自动刷新浏览器

2.6 Source Map  源代码地图

2.7 模块热更新 Webpack HMR

2.8 webpack 不同环境下的配置

2.9 Tree-Shaking usedExports 去除未使用的代码

2.10 webpack 代码分割

2.11 文件名hash

2.12 browserslistrc 平台支持(兼容)

2.13 postcss 处理一些需要兼容的css(加浏览器前缀)

2.14 asset 处理图片,字体资源 

2.15 polyfill 填充 兼容更新的js promise等

2.16 resolve文件名称解析

2.17 MiniCssExtractPlugin做Css抽离和压缩

2.18 TerserPlugin 压缩TS

2.19 scope hoisting 作用域提升 减少作用域链的查找

2.20 compression-webpack-plugin资源压缩

2.12 speed-measure-webpack-plugin 打包时间和内容分析

一、ES Modules

1.基本特性

<script type="module"></script>

  • ESM 默认使用严格模式 (无this)
  • 每个Module都是运行在单独的私有作用域中
  • ESM都是通过CORS的方式请求外部JS模块的 (浏览器要支持CORS)
  • ESM的Script标签会延迟执行脚本 (同defer)

2.ES Module的导入导出

//导出成员是只读的

//导出的是成员的地址
//x.js
export let name = {}

import { name } from './x.js'

//a.js
let obj = {}
export default obj

import obj from './a.js'



import {} from './a.js' // 代表执行该模块
import { name } from 'http://xxx/xxx' // 可以从远程地址导入
import * as mod from './a.js' // 代表导入a.js中的所有模块
//import 不能从变量导入,如果需要动态导入
import('./mod.js').then(function(module){
   console.log(module)
})

//导出默认成员和命名成员
import { name, default as title } from 'a.js'
//或
import title, { name } from 'a.js'

3.ES Modules 运行环境的兼容性问题

polyfill兼容方案,通过script引入

 支持浏览器的会执行两遍

解决方案

<script nomodule src="xxxx"></script>

4.Node.js对ES Modules的支持情况

node 版本8以上

执行语句:node --experimental-modules index.mjs

import fs from 'fs'  引入系统内置模块

5.Node 环境中,ES Modules 载入CommonJs模块

  •  ES Modules中可以导入CommonJs
  •  CommonJs模块始终只会导出一个默认的成员,所以不能使用import { xx } from 'xxx'的方式导入CommonJs的内容 。即不能直接提取成员
  • CommonJs不能导入ESModules(不能在CommonJs模块中通过require载入ES Modules)

二、Webpack

前端整体的模块化 不单单是JS

1.webpack命令

yarn init

yarn add webpack webpack-cli --dev

yarn webpack --version

yarn webpack

2.Webpack 配置文件

2.1 webpack.config.js

const path = require('path')

// 自动清除dist目录文件的插件
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
// webpack自动输出HTML文件
const HTMLWebpackPlugin = require('html-webpack-plugin')
// public下的文件拷贝到输出目录
const CopyWebpackPlugin = require('copy-webpack-plugin')


module.exports = {
    mode: 'none', // 模式 development production  none
        entry: './src/index.js', // 入口
        output: {  // 输出文件路径
            filename: 'bundle.js',
            path: path.join(__dirname, 'dist'),
            // 根目录
            // publicPath: 'dist/'
        },
        // treeShaking去除冗余代码 生产环境不需要 会自动开启
        optimization: {
            usedExports: true, // 标记未使用代码 只导出被使用的成员
            minimizer: true, // 去除未使用代码 压缩输出结果
            concatenateModules: true, // 合并模块代码到同一个函数中
            // sideEffects: true //去除模块副作用  没有用到的模块不会被打包()  ['./src/s.js']
            // minimizer: [
                  //压缩打包后的css文件
            //     new OptimizeCssAssetsWebpackPlugin()
            //     new TerserWebpackPlugin() // js 压缩
            // ]
        },
        devtool: 'cheap-module-eval-source-map',
        // webpack-dev-server
        devserver: {
            // 静态资源目录
            contentBase: './public',
            // 开启热更新
            hot: true,
            // hotonly: true, 热更新错误也不会刷新页面
            // 代理
            proxy: {
                '/api': {
                    target: 'http://xx.com',
                    // 路径替换 
                    pathRewrite: {
                        '^/api': '',
                    },
                    // 实际代理主机名
                    changeOrigin: true
                }
            }
        },
        // loader 加载任何类型的资源
        module: {
            rules: [
                // css loader
                {
                    test: /.css$/,
                    // use 从后往前执行
                    use: [
                        'style-loader',
                        'css-loader'
                    ]
                },
                // 文件资源加载器
                {
                    test: /.png$/,
                    use: 'file-loader' // 大文件使用
                }, 
                {
                    test: /.png$/,
                    use: {
                        loader: 'url-loader', // 'url-loader  将图片转变为base64  data urls (小文件使用 Data URLs)
                        options: {
                            limit: 10 * 1024  // 10KB
                        }
                    }
                },
                // es6语法转换babel-loader
                {
                    test: /.js$/,
                    use: {
                        loader: 'babel-loader',
                        options: {
                            presets: [
                                ['@babel/preset-env', { modules: 'false'}]
                            ]
                        }
                    }
    
                },
                // html-loader  html文件中,img 的资源 a标签的资源
                {
                    test: /.html$/,
                    use: {
                        loader: 'html-loader',
                        options: {
                            attrs: ['img:src', 'a:href']
                        }
                    }
                }
            ]
        },
        // 插件 plugin
        Plugins: [
            // 自动清除插件
            new CleanWebpackPlugin(),
            // webpack自动输出HTML文件  生成index.html
            new HTMLWebpackPlugin({
                title: '标题',
                meta: {
                    viewport: 'width=device-width'
                },
                // 模板路径
                template: './src/index.html'  // 文件中使用<%= htmlWebpackPlugin.options.title %> 传变量
            }),
            // 生成about.html
            new HTMLWebpackPlugin({
                filename: 'about.html'
            }),
            // 拷贝文件到dist目录  开发阶段不需要使用
            new CopyWebpackPlugin([
                'public'
            ]),
            // HRM热更新
            new webpack.HotModuleReplacementPlugin(),
            // deginePlugin 全局注入变量 process.env.Node-env
            new webpack.DefinePlugin({
                API_BASE_URL: JSON.stringify('http://api.example.com') // 应该是代码片段
            }),
            // css模块打包成一个文件
            // new MiniCssExtraPlugin(), // 使用此插件 不需要使用style-loader 替换为 MiniCssExtraPlugin.loader  (link的方式注入样式文件)
        ]

}

2.2 loader分类

  • 编译转换类  css-loader babel-loader style-loader sass/less-loader 
  • 文件操作类  file-loader url-loader
  • 代码检查类 eslint-loader

2.3 webpack工作原理

递归查找依赖项 并进行打包

 

2.4 自定义loader和plugin

loader: 返回的必须是js


const marked = require('marked')
module.exports = source => {
    //必须返回js代码
    const html = marked(source);
    return `module.exports = ${JSON.stringify(html)}`
}

plugin: 返回的必须是一个函数或包含apply方法

// 删除dist中的注释
class MyPlugin {
    apply(compiler) {
      // compiler 包含webpack的各种钩子
       compiler.hooks.emit.tap('MyPlugin', compilation => {
            // compilation  此次打包的上下文
            for(let name in compilation.assets) {
               // 判断是否是js结尾的
               if(name.endsWith('.js')) {
                const contents = compilation.assets[name].source();
                const withoutComments = contents.replace(/\/\*\*+\*\//g, '');
                compilation.assets[name] = {
                    source: () => withoutComments,
                    size: () => withoutComments.length
                  }
               }
                }
               })
            }
  }

2.5 webpack dev server 自动编译和自动刷新浏览器

// 打包结果暂存在内存中 提高效率
yarn add webpack-dev-server --D

yarn webpack server

// webpack.config.js

devserver: {
        // 热更新
        hot: true,
        // 本地服务所在的目录 和output里的publicPath保持一致
        // publicPath: '',
        // 静态资源目录
        contentBase: './public',
        // 监控contentBase里内容的变化
        watchContentBase: true,
        // 代理
        proxy: {
            '/api': {
                target: 'http://xx.com',
                // 路径替换 
                pathRewrite: {
                    '^/api': '',
                },
                // 实际代理主机名
                changeOrigin: true
            }
        }
    },

2.6 Source Map  源代码地图

 

 

// webpack.config.js
devtool: 'source-map'


// eval 是否使用eval执行模块代码
// cheap 包含行信息
// module 是否能够得到loader处理之前的源代码

// devtool的取值
source-map
eval  // 只能定位错误文件
eval-source-map // 可定位到具体行列
cheap-eval-source-map // 可定位到具体行, es6转换后的结果
cheap-module-eval-source-map // 可定位到具体行  源代码
cheap-source-map //loader 处理过后的代码 定位到行
inline-source-map // 定位到行列 最不常用 data-url的方式
nosources-source-map // 开发工具中看不见源代码 但是有行列信息 生产环境下常用

开发环境下常使用的模式

cheap-module-eval-source-map  首次打包慢 重写打包快

生产环境下

none 不生成打包map

2.7 模块热更新 Webpack HMR

避免刷新后状态丢失问题

在webpack.config.js中开启:

const webpack = require('webpack')
module.exports = {
   devserver: {
        // 开启热更新
        hot: true,
    },
  Plugins: [
  // HMR热更新
   new webpack.HotModuleReplacementPlugin()
  ]
}

以上只能达到css的热替换

js png的热替换需要自己定义 module.hot.accept('文件', () => {处理逻辑})

2.8 webpack 不同环境下的配置

yarn webpack   开发环境命令

yarn webpack --env production 生产环境命令

小型项目 通过判断env的值 判断是哪个环境 

const path = require('path')

// 自动清除dist目录文件的插件
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
// webpack自动输出HTML文件
const HTMLWebpackPlugin = require('html-webpack-plugin')
// public下的文件拷贝到输出目录
const CopyWebpackPlugin = require('copy-webpack-plugin')

const webpack = require('webpack')


module.exports = (env, argv) => {
    const config = {
        mode: 'none', // 模式 development production  none
        entry: './src/index.js', // 入口
        output: {  // 输出文件路径
            filename: 'bundle.js',
            path: path.join(__dirname, 'dist'),
            // 根目录
            // publicPath: 'dist/'
        },
        devtool: 'cheap-module-eval-source-map',
        // webpack-dev-server
        devserver: {
            // 静态资源目录
            contentBase: './public',
            // 开启热更新
            hot: true,
            // hotonly: true, 热更新错误也不会刷新页面
            // 代理
            proxy: {
                '/api': {
                    target: 'http://xx.com',
                    // 路径替换 
                    pathRewrite: {
                        '^/api': '',
                    },
                    // 实际代理主机名
                    changeOrigin: true
                }
            }
        },
        // loader 加载任何类型的资源
        module: {
            rules: [
                // css loader
                {
                    test: /.css$/,
                    // use 从后往前执行
                    use: [
                        'style-loader',
                        'css-loader'
                    ]
                },
                // 文件资源加载器
                {
                    test: /.png$/,
                    use: 'file-loader' // 大文件使用
                }, 
                {
                    test: /.png$/,
                    use: {
                        loader: 'url-loader', // 'url-loader  将图片转变为base64  data urls (小文件使用 Data URLs)
                        options: {
                            limit: 10 * 1024  // 10KB
                        }
                    }
                },
                // es6语法转换babel-loader
                {
                    test: /.js$/,
                    use: {
                        loader: 'babel-loader',
                        options: {
                            presets: ['@babel/preset-env']
                        }
                    }
    
                },
                // html-loader  html文件中,img 的资源 a标签的资源
                {
                    test: /.html$/,
                    use: {
                        loader: 'html-loader',
                        options: {
                            attrs: ['img:src', 'a:href']
                        }
                    }
                }
            ]
        },
        // 插件 plugin
        Plugins: [
            // 自动清除插件
            new CleanWebpackPlugin(),
            // webpack自动输出HTML文件  生成index.html
            new HTMLWebpackPlugin({
                title: '标题',
                meta: {
                    viewport: 'width=device-width'
                },
                // 模板路径
                template: './src/index.html'  // 文件中使用<%= htmlWebpackPlugin.options.title %> 传变量
            }),
            // 生成about.html
            new HTMLWebpackPlugin({
                filename: 'about.html'
            }),
            // 拷贝文件到dist目录  开发阶段不需要使用
            new CopyWebpackPlugin([
                'public'
            ]),
            // HRM热更新
            new webpack.HotModuleReplacementPlugin()
        ]
    
    }

    if (env === 'production') {
        config.mode = 'production';
        config.devtool = false;
        config.Plugins = [
            ...config.Plugins,
            new CleanWebpackPlugin(),
            new CopyWebpackPlugin(['public'])
        ]
    }
    return config;
}

大型项目可以使用不同环境使用不同的配置文件

webpack.dev.js  webpack.prod.js webpack.common.js

yarn add webpack-merge -D  合并webpack配置的插件

const common = require('./webpack.common.js')
const merge = require('webpack-merge)


module.exports = merge(common, {
...
})

运行配置文件需要使用 yarn webpack --config webpack.prod.js

2.9 Tree-Shaking usedExports 去除未使用的代码

生产环境会自动开启

开发环境下开启方式

// treeShaking去除冗余代码 生产环境不需要 会自动开启
optimization: {
   usedExports: true, // 标记未使用代码
   minimize: true, // 去除未使用代码
   // 去除未使用模块(export 导出的模块)  副作用清除
   sideEffects: true
},

Tree Shaking 只能转换ESModules模式的代码

@babel/preset-env 会将ESModule转换成commonJS,所以在配置时 要禁止转化

{
      test: /.js$/,
      use: {
         loader: 'babel-loader',
         options: {
           presets: [
              ['@babel/preset-env', { modules: 'false'}]
                     ]
                   }
             }
    
 }

 

2.10 webpack 代码分割

分包 按需加载

方式: 1.多入口打包   多页面程序 

                entry: { index: 'xxx', album: 'xxx'},

                output: { filename: '[name].bundle.js'}

                 new HtmlWebpackPlugin({filename: 'index.html', chunks: ['index']})

    公共模块的提取

optimization: {
   splitChunks: {
     chunks: 'all'
   }
},

            2.esModules动态导入

                动态导入的模块会自动分包 生成自己的bundle

                 import('xxx').then((default: xxx) => {})

                 自动分包的模块命名

                  import(/*webpackChunkName: 'name'*/'xxx').then(() => {})

2.11 文件名hash

filename: '[name]-[hash:8].bundle.js'
// hash 整个文件hash值一样
// chunkhash  模块hash值
//contenthash 文件hash  解决缓存问题

2.12 browserslistrc 平台支持(兼容)

在根目录下创建 .browserslistrc文件 ,设置需要支持的平台

  ">1%",
  "last 2 version",
  "not dead"

在package.json里配置

"browserslist": [
  ">1%",
  "last 2 version",
  "not dead"
]

2.13 postcss 处理一些需要兼容的css(加浏览器前缀)

// 通过命令行运行
yarn add postcss postcss-cli autoprefixer -D

postcss-cli 命令
yarn postcss --use autoprefixer -0 ret.css ./src/css/tsetcss


//在webpack的配置项中使用
yarn add postcss-loader postcss-preset-env-D
// postcss-preset-env 各种postcss 插件的集合 加前缀 颜色8位 rgba等
{
  test: /\.css$/,
  use: ['style-loader',
   {
     loader: 'css-loader',
     options: { importLoaders: 1, esModule: false} // 当在css文件中发现@import导入的css需要进行前缀添加,后退一步 esModule处理url('xxx')
   }, 
   {
     loader: 'postcss-loader',
     options: { postcssOptions: { plugins: [require('postcss-preset-env')]}}
   }]
}



// options中的预设也可以单独设置文件
//postcss.config.js
module.exports = {
  plugins: [require('postcss-preset-env')]
}

会自动的在css前添加前缀

2.14 asset 处理图片,字体资源 

之前是使用 url-loader file-loader 现在可使用asste代替

// 1 asset/resource  -> file-loader
// 2 asste/inline -> url-loader
// 3 asste/source -> raw-loader
// 4 asset 做limit限制

//output: {
//  assetModuleFilename: 'img/[name].[hash.6][ext]'
// },

// module
{
  test: /\.(png|svg|gif|jpg?g)$/,
  type: 'asset/resource',
  generator: {
   finename: 'img/[name].[hash.6][ext]'
  }
}

{
  test: /\.(png|svg|gif|jpg?g)$/,
  type: 'asset/inline',
}

{
  test: /\.(png|svg|gif|jpg?g)$/,
  type: 'asset',
  generator: {
   finename: 'img/[name].[hash.6][ext]'
  },
  parser: {
    dataUrlCondition: {
      maxSize: 30 * 1024
    }
  }
}

//处理字体
{
  test: /\.(ttf|woff2?)$/,
  type: 'asset/resource',
  generator: {
   finename: 'img/[name].[hash.6][ext]'
  }
}

2.15 polyfill 填充 兼容更新的js promise等

preset-env可能不能完全的转换js polyfill打补丁 转换未转换的js

yarn add core-js regenerator-runtime

// babel.config.js

// useBuiltIns: false 不对当前的JS处理做polyfill填充
// useBuiltIns: usage, corejs: 3 依据用户源代码中所使用的新语法进行填充
//  useBuiltIns: entry  根据当前筛选出的浏览器做填充

module.exports = {
   presets: [
    ['@babel.preset-env', { useBuiltIns: false }]
   ]
}

2.16 resolve文件名称解析

默认情况下引入路径需要将后缀名写全,在这里配置后,就不需要带后缀

resolve: {
  extensions: [".js", ".json", ".ts", ".vue"], // 自动补全这些后缀名
  alias: {
    '@': path.resolve(__dirname, 'src') // 别名
          }
 },

2.17 MiniCssExtractPlugin做Css抽离和压缩

一般在生产环境使用

const MiniCssExtractPlugin = require('minicss-extract-plugin')

//plugins
new MiniCssExtractPlugin({
  filename: '[name].[hash:8].css'
})

//loader: css
MiniCssExtractPlugin.loader 替换style-loader

2.18 TerserPlugin 压缩TS

webpack5不需要单独安装 webpack4需要单独安装

optimization: {
  minimizer: {
   new TerserPlugin();
  }
}

2.19 scope hoisting 作用域提升 减少作用域链的查找

webpack内置的插件

new webpack.optimize.ModuleConcatenationPlugin();

2.20 compression-webpack-plugin资源压缩

服务器会返回给前端压缩后的文件内容

const CompressionPlugin = require('compression-webpack-plugin')

new CompressionPlugin({
   test: /\.(css|js)$/,
   //(压缩前/压缩后)
   miniRatio: 0.8, // 一般默认即可
   // 压缩算法
   algorithm: 'gzip'
});

2.12 speed-measure-webpack-plugin 打包时间和内容分析

const speedMeasurePlugin = require('speed-measure-webpack-plugin')

const smp = new speedMeasurePlugin();

const webpackConfig = smp.wrap({
  ...
})

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值