模块化

模块化

  • 演变过程

阶段一. 按文件划分,每个文件就是一个独立模块,代码中调用全局成员
问题:污染全局 命名冲突 完全依靠约定

阶段二: 命名空间 每个模块只暴露一个全局对象,模块成员挂到对象下面
问题: 没有私有空间,模块成员仍然可以被修改,模块依赖关系问题

阶段三: 立即执行函数内部挂载对象,对象上挂载对外成员,私有成员在函数内部

commonjs标准

  • 一个文件都是一个模块
  • 每个模块都有单独作用域
  • module.exports导出成员
  • require载入模块
  • 同步模式加载模块

AMD

ESModule

  • 自动采用严格模式
  • 每个模块都要单独作用域
  • 通过cors去请求外部js
  • script标签延迟执行



  
  
  
  ES Module - 模块的特性


  
  

  
  

  
  
  

  
  

  
  
  

需要显示的内容

export import

  • export { }是固定写法,不是导出对象的意思
  • export default 后面可以接不同类型变量
  • import { } 对应export
  • import name 对应 export default
  • 对外暴露的成员暴露的是引用关系
  • 导入的成员是只读成员
  • import 引入文件名路径要完成不能省略,相对路径不能省略,省略会认为在加载第三方模块,可以是绝对路径 url
  • import {} from './module' 或者 import './module' 执行模块, 而不提取具体成员
  • import * as obj from './module'导入所有模块,所有模块存储在对象中
  • import from不能动态导入 ,可以使用import()函数动态导入,返回一个promise
import('./module').then(module=>{
console.log(module)
})
  • 默认导出与export导出同时存在
    import {a,b,c as default} from './modules'
    简写 import c, {a,b} from './modules'

  • 导入模块作为导出成员 export {a,b} from './module'

  • script type="nomodule" //不支持 ES Module的浏览器才会执行

  • es中可以导入commonjs模块,作为默认导出,反之不可以

webpack

  • npm init初始化package.json文件
  • 使用 cnpm i webpack webpack-cli -D
  • npx webpack 默认按照 src/index.js打包
  • 自定义配置 项目下新建 webpack.config.js
  • webpack --mode development/production/none 开发模式/生产模式打包/原始状态打包 也可以在配置文件中设置 mode属性
const path = require('path')

module.exports = {
  // 这个属性有三种取值,分别是 production、development 和 none。
  // 1. 生产模式下,Webpack 会自动优化打包结果;
  // 2. 开发模式下,Webpack 会自动优化打包速度,添加一些调试过程中的辅助;
  // 3. None 模式下,Webpack 就是运行最原始的打包,不做任何额外处理;
  mode: 'development',
  entry: './src/main.js',
  output: {
    filename: 'bundle.js',
    path: path.join(__dirname, 'dist')
  }
}

资源加载

  • 加载其他资源通过指定对应loader来解析对应文件
  • module属性 配置 rules来指定对其他资源加载规则
  • rules中可以指定多个loader,从后往前执行 css-loader把css转换为js模块,style-loader把对应模块以style标签形式插入html中
var path = require("path")
module.exports = {
    mode:'development',
    entry: './src/main.js',
    output: {
        path: path.resolve(__dirname, "./dist"),
        filename: '.js/bundle.[name].[hash].js'
    },
    module:{
        rules:[
              {
                  test:/.css$/,
                  use:['style-loader','css-loader']
              }
        ]
    }
}

文件资源 loader

  • 加载图片 cnpm i file-loader -D
  • base64加载资源 cnpm i url-loader -D
  test: /\.(png|jpg|gif)$/,
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            limit:10*1024,//10k一下url-loader处理 以上自动使用file-loader
                            esModule: false
                        }
                    }
 ],

常用loader

  1. 编译转换类loader ex. css-loader => css转换为js模块
  2. 文件操作类型loader 把文件输出到对应目录,同时导出文件的访问路径 ex.file-loader
  3. 代码检查loader 统一代码风格 eslint-loader

webpack 加载资源的方式

  • 兼容 Es module CommonJs AMD
  • 加载的非js也会触发资源加载对应的loader ex. css中 @import url img的src
  • html-loader a标签href默认不会触发资源加载,可以在options只配置
 {
        test: /.html$/,
        use: {
          loader: 'html-loader',
          options: {
            attrs: ['img:src', 'a:href']
          }
        }
      }

webpack工作原理

  • 入口js中通过import/require找到对应的文件依赖,分别取解析每一个资源模块对应的依赖,形成一个依赖树,递归资源树,找到对应对应资源配置的加载器去加载对应模块,最后把加载结果放到bundle.js

es6转换

  • cnpm i babel-loader @babel/core @babel/preset-env -D
 {
        test: /.js$/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env']
          }
        }
      },

自定义loader

  • 项目根目录新建markdown-loader.js
  • loader返回结果必须是一段js代码,方便交给下一个loader处理
  • 负责输入到输出的转换 类型管道,同一个资源可以使用多个loader
const marked = require('marked')

module.exports = source => {
  //source接受输入
  const html = marked(source)
  // 返回 html 字符串   交给下一个 html-oader 处理  
  return html 
}

webpack.config.js配置

  rules: [
      {
        test: /.md$/,
        use: [
          'html-loader',
          './markdown-loader'  //从后往前执行
        ]
      }
    ]

plugin

  • 解决除了资源加载之外的自动化工作 ex:清除目录 压缩代码 拷贝静态资源到输出目录
  • 常用插件 clean-webpack-plugin(清除目录) html-webpack-plugin(生成html)
const path = require('path')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')

module.exports = {
  mode: 'none',
  entry: './src/main.js',
  output: {
    filename: 'bundle.js',
    path: path.join(__dirname, 'dist'),
    // publicPath: 'dist/'
  },
  module: {
    rules: [
      {
        test: /.css$/,
        use: [
          'style-loader',
          'css-loader'
        ]
      },
      {
        test: /.png$/,
        use: {
          loader: 'url-loader',
          options: {
            limit: 10 * 1024 // 10 KB
          }
        }
      }
    ]
  },
  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'
    }),
    new CopyWebpackPlugin([
      // 'public/**'
      'public'
    ])
  ]
}


自定义插件 钩子机制

  • 通过在声明周期钩子中挂载函数来扩展
class MyPlugin {
  apply (compiler) {
    console.log('MyPlugin 启动')
   //webpack即将往输出目录输出文件执行
    compiler.hooks.emit.tap('MyPlugin', compilation => {
      // compilation => 可以理解为此次打包的上下文
      for (const name in compilation.assets) {
        // console.log(name)
        // console.log(compilation.assets[name].source())
        if (name.endsWith('.js')) {
          const contents = compilation.assets[name].source()
          const withoutComments = contents.replace(/\/\*\*+\*\//g, '')
          compilation.assets[name] = {
            source: () => withoutComments,
            size: () => withoutComments.length
          }
        }
      }
    })
  }
}

监视模式运行 webpack --watch

  • 编译自动刷新 cnpm i webpack-dev-server
  • npx webpack-dev-server --open 自动运行webpack进行打包并启动开发服务器
    此时会监听文件变化自动刷新浏览器
    并不会生成dist目录,打包结果暂存在内存中,http-server从内存中读取资源发送给浏览器解析
  • devServer配置静态资源访问路径 一般是开发阶段配置静态资源访问路径,生成上线打包public目录
 devServer:{
       contentBase:'./public'
    },

代理

 devServer:{
       proxy:{
       '/api':{ 
           target:'https://xxxxx',
            //路径重写
            pathRewrite:{
                 '^api':''
              },
           //使用实际请求的地址作为主机名
            changeOrigin:true
        }
      }
    },

sourceMap

  • 解决编写代码与运行代码不一致产生的调试问题
  • jquery末尾 中 添加 //# sourceMappingURL=jquery-3.4.1.min.map生成源码
  • webpack 配置 devtool:'source-map'

sourceMap 类型

  • eval模式 找问题代码是打包过后的模块代码,每个模块转换过后的代码放在eval函数中执行 执行最后通过 //# sourceURL = xx指定源码地址 定位错误出现的文件,不会生成sourceMap文件,构建速度最快,但无法直接找到错误具体行信息
  • eval-source-map 想必eval生成了sourceMap ,可以查看错误信息行列信息
  • cheap-eval-source-map 比eval-source-map轻量 只能看到错误行信息,速度更多
  • cheap-module-eval-source-map 于cheap-eval-source-map 想比显示的是没有经过es6编辑的代码
  • inline-source-map 把sourceMap以base64嵌入源代码中
  • hidden-source-map 生成了sourceMap文件,代码中并没有引入对应map文件
  • nosource-source-map 能看到错误的行列信息,但看不到源代码
  • webpack.config.js可以导出一个数组,产生多少打包结果

选择souceMap

  • 开发环境 cheap-module-eval-source-map
  • 生成环境 none/nosource-source-map
//相同入口可以生成  a.js b.js
module.exports = [
    {
        entry: './src/main.js',
        output: {
            filename: 'a.js'
        }
    },
    {
        entry: './src/main.js',
        output: {
            filename: 'b.js'
        }
    }
]

HMR

  • 模块热更新 页面不刷新情况下,更新模块
  • 集成在webpack-dev-server中 webpack-dev-sercer --hot开启或者在配置文件中配置
  • webpack-dev-server hot选项设置为true plugin中
    new webpack.HotModuleReplacementPlugin()
  • js文件热更新需要自定义处理模块替换逻辑,默认会自动刷新,样式文件经过loader处理,loader中处理过了热更新,不需要自定义处理
//注册对应模块发生变化的处理函数
module.hot.accept('./edit',function(){
   处理逻辑
})

hot改为hotOnly 出现错误,不会自动刷新

不同环境下配置

1.配置文件中添加判断
配置文件导出一个函数 函数中返回配置项
接受两个参数,一个是cli 命名传递的参数 一个是运行传递的所有参数
-- npx webpack --env production 传递参数打包生成环境

const webpack = require('webpack')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')

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'
          ]
        },
        {
          test: /\.(png|jpe?g|gif)$/,
          use: {
            loader: 'file-loader',
            options: {
              outputPath: 'img',
              name: '[name].[ext]'
            }
          }
        }
      ]
    },
    plugins: [
      new HtmlWebpackPlugin({
        title: 'Webpack Tutorial',
        template: './src/index.html'
      }),
      new webpack.HotModuleReplacementPlugin()
    ]
  }

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

  return config
}

2 多配置文件方式

  • webpack.dev.js webpack.common.js webpack.prod.js
  • webpack-merge追加新的配置
  • npx webpack --config 配置文件名 进行打包
    common
const path = require("path")
const webpack = require('webpack')
const merge = require('webpack-merge')
const VueLoaderPlugin = require('vue-loader/lib/plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
module.exports = {
    devServer:{
       contentBase:'./public'
    },
    entry: './src/main.js',
    output: {
        path: path.resolve(__dirname, "./dist"),
        filename: 'bundle.js'
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                loader: 'babel-loader',
                exclude: /node_modules/,
            },
            {
                test: /\.vue$/,
                loader: ['vue-loader'],
                exclude: /node_modules/,
            },

            {
                test: /\.less$/,
                use: ['style-loader', 'css-loader', 'less-loader'],
                exclude: /node_modules/,
            },
            {
                test: /\.css$/,
                use: [
                    'style-loader',
                    'css-loader'
                ],
                exclude: /node_modules/,
            },
            {
                test: /\.(png|jpg|gif)$/,
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            limit: 10 * 1024,//10k一下url-loader处理 以上自动使用file-loader
                            esModule: false
                        }
                    }
                ],
                exclude: /node_modules/,

            }
        ]
    },
    plugins: [
        new CleanWebpackPlugin(),
        new VueLoaderPlugin(),
        new htmlWebpackPlugin({
            title: 'spa单页应用',
            url: '',
            filename: 'index.html',
            template: './index.html'
        })
    ],
}

dev

// 开发环境配置
const merge = require('webpack-merge') // webpack 合并配置插件 详细了解==>(https://github.com/survivejs/webpack-merge)
const common = require('./webpack.common.js') // 引入公共模块配置
const webpack = require('webpack') // 引入webpack
module.exports = merge(common, {
    devtool: 'cheap-module-eval-source-map', 
    devServer: {
        contentBase: './dist'
    },
    mode: 'development',
})

prod

// 配置生产环境
const merge = require('webpack-merge')
const common = require('./webpack.common.js')
const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); 

module.exports = merge(common, {
    mode: 'production',
    devtool:'none',
    plugins: [
        new UglifyJSPlugin()
        new CopyWebpackPlugin(
            {
                patterns: [
                    {
                        from: path.resolve(__dirname, './public'), //定义要拷贝的源目录,必填项
                        to: 'public' //定义要拷贝到的目标目录,非必填,不填写则拷贝到打包的output输出地址中
                    }

                ]
            }
        ),
    ],
})

DefinePlugin

  • 向全局注入全局变量
const webpack = require('webpack')

module.exports = {
  mode: 'none',
  entry: './src/main.js',
  output: {
    filename: 'bundle.js'
  },
  plugins: [
    new webpack.DefinePlugin({
      // 值要求的是一个代码片段
      API_BASE_URL: JSON.stringify('https://api.example.com')
    })
  ]
}

tree-shaking

  • 去掉未引用代码
  • babel-loader使用失效,tree-shaking处理的必须是esModule
    rules: [
      {
        test: /\.js$/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: [
              // 如果 Babel 加载模块时已经转换了 ESM,则会导致 Tree Shaking 失效
              // ['@babel/preset-env', { modules: 'commonjs' }]
              // ['@babel/preset-env', { modules: false }]
              // 也可以使用默认配置,也就是 auto,这样 babel-loader 会自动关闭 ESM 转换
              ['@babel/preset-env', { modules: 'auto' }]
            ]
          }
        }
      }
    ]
    optimization:{
        usedExports:true,
     // 尽可能合并每一个模块到一个函数中
       concatenateModules: true,
        minimize:true //开启压缩
  },
副作用 sideEffects
  • 模块执行时除了导出成员之外所做的事情
  • 一般用于npm包标记是否有副作用
  • package.json配置 "sideEffects":"false" 标识项目代码没有副作用
  • 一个组件a.js文件引入所有组件,另一个b.js引入了一个a.js中的一个组件,但由于a.js引入了所有组件,打包就会打包所有组件,为了解决这个问题,采用副作用处理,没有用到的组件模块就不会打包
  • 要确定项目的副作用,否则使用该功能会误删有作用的代码
  • package.json 可以设置哪些文件有副作用
 "sideEffects": [
    "./src/extend.js",
    "*.css"
  ]
optimization: {
    sideEffects: true,   //开启副作用功能
    // 模块只导出被使用的成员
    // usedExports: true,
    // 尽可能合并每一个模块到一个函数中
    // concatenateModules: true,
    // 压缩输出结果
    // minimize: true,
  }

代码分割

  • 应用比较大时,所有模块打包到一起,会造成体积很大影响加载速度
  • 分包 按需加载 采用动态导入或者多入口打包来解决
  1. 多入口打包
  • entry采用对象写法配置多入口
  • output filename: '[name].bundle.js' //动态输出文件名 name最后会被替换为入口文件的名称
  • HtmlWebpackPlugin 指定chunk来指定输出html的对应打包js
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  mode: 'none',
  entry: {
    index: './src/index.js',
    album: './src/album.js'
  },
  output: {
    filename: '[name].bundle.js'  //动态输出文件名  name最后会被替换为入口文件的名称
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader',
          'css-loader'
        ]
      }
    ]
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title: 'Multi Entry',
      template: './src/index.html',
      filename: 'index.html',
      chunks: ['index']
    }),
    new HtmlWebpackPlugin({
      title: 'Multi Entry',
      template: './src/album.html',
      filename: 'album.html',
      chunks: ['album']
    })
  ]
}

提取公共模块

  optimization: {
    splitChunks: {
      // 自动提取所有公共模块到单独 bundle
      chunks: 'all'
    }
  },

按需加载

  • 需要某个模块才去加载对应模块
  • 采用动态导入,会被自动分包 import('xx').then(module=>{ })
  • /* webpackChunkName: 'components' */ 给分包起名,默认是数字,如果名字起一样,就会被打包到一个文件中
  if (hash === '#posts') {
    // mainElement.appendChild(posts())
    import(/* webpackChunkName: 'posts' */'./posts/posts').then(({ default: posts }) => {
      mainElement.appendChild(posts())
    })
  } else if (hash === '#album') {
    // mainElement.appendChild(album())
    import(/* webpackChunkName: 'album' */'./album/album').then(({ default: album }) => {
      mainElement.appendChild(album())
    })
  }

MiniCssExtractPlugin

  • 提取css到单独文件
  • css文件超过一定体积如150kb,才需要考虑提取文件
  • webpack默认压缩不会压缩css需要单独插件optimize-css-assets-webpack-plugin
  • terser-webpack-plugin防止css压缩覆盖js压缩
  • minimizer生成环境默认开启,下面对应插件才会运行
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
const TerserWebpackPlugin = require('terser-webpack-plugin')

module.exports = {
  mode: 'none',
  entry: {
    main: './src/index.js'
  },
  output: {
    filename: '[name].bundle.js'
  },
  optimization: {
    minimizer: [
      new TerserWebpackPlugin(),
      new OptimizeCssAssetsWebpackPlugin()  
    ]
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          // 'style-loader', // 将样式通过 style 标签注入
          MiniCssExtractPlugin.loader,
          'css-loader'
        ]
      }
    ]
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title: 'Dynamic import',
      template: './src/index.html',
      filename: 'index.html'
    }),
    new MiniCssExtractPlugin()
  ]
}

文件hash

  • 生产环境资源文件配置hash防止文件修改缓存
  • output与plugin filename 一般支持hash
  1. [name]-[hash].bound.css 项目级别hash 只要项目任何地方改动,当前hash就会发生改变
  2. [name]-[chunkhash].bound.css 同一路的文件发生改变,hash才会改变
  • [name]-[contenthash].bound.css 文件hash 文件发生改变才会触发hash改变
  • [name]-[contenthash:8].bound.css :数字指定hash长度

你可能感兴趣的:(模块化)