美文网首页
模块化开发

模块化开发

作者: lowpoint | 来源:发表于2021-01-25 23:19 被阅读0次

模块化开发是一种思想,随着前端项目的日益庞大。为了使我们开发协作更加高效,互不影响。将编写的代码模块化,更利于协作与维护。使得开发更加高效。

CommonJs规范

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

CommonJS是以同步模式加载模块
因为node执行机制是在启动时加载模块,执行过程中不会加载模块,这种方式在node中不会有问题。但是在浏览器端,使用每次页面加载都会导致大量同步请求出现。所以当时有一个特地为浏览器设定的规范 AMD

AMD(Asynchronous Module Definition)异步模块定义规范

用define定义一个模块

//定义一个模块
define('module',['jquery','./module2'],function($,module2){
  return {
    fn:function(){ console.log('hello')}
  }
})

//加载一个模块
require(['./module'],function(module){
  module.fn()
})
  • AMD使用起来相对复杂
  • 模块JS文件请求频繁 页面效率低下

模块化标准规范

在nodeJS中 使用 CommonJS 规范
在浏览器环境中 使用 ESModules 规范

ES Modules

基本特性

  • ESM 自动采用严格模式 忽略 'use strict'
  • 每个ESM 都是运行在单独的私有作用域中
  • ESM是通过 CORS 的方式请求外部JS模块的 必须在http serve环境中运行
  • ESM script标签会延迟执行脚本 相当于添加defer属性
  <!-- 通过 script 添加 type=module 属性,就可以使 ES Module标准执行js -->
<script type="module">
    console.log('sssssss')
  </script>

导入导出

export const foo = 'esm'
import { foo } from './module.js'

ES Module 浏览器环境 Polyfill

在支持新语法的浏览器中 脚本执行一次,引入polyfill也会执行一次代码,就会出现执行两次现象。此时,引入nomodule 属性,就只会在不支持的浏览器中工作。(只适用于开发阶段,生产环境还是要先进行编译处理)

<!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>ES Module 浏览器环境 Polyfill</title>
</head>
<body>
  <script nomodule src="https://unpkg.com/promise-polyfill@8.1.3/dist/polyfill.min.js"></script>
  <script nomodule src="https://unpkg.com/browser-es-module-loader@0.4.1/dist/babel-browser-build.js"></script>
  <script nomodule src="https://unpkg.com/browser-es-module-loader@0.4.1/dist/browser-es-module-loader.js"></script>
  <script type="module">
    import { foo } from './module.js'
    console.log(foo)
  </script>
</body>
</html>

node.js中使用ES Module

js文件扩展名修改为.mjs后缀
命令行启动 node --experimental-modules index.mjs (启用ESModule实验特性)

import { foo, bar } from './module.mjs'

console.log(foo, bar)

// 此时我们也可以通过 esm 加载内置模块了
import fs from 'fs'
fs.writeFileSync('./foo.txt', 'es module working')

// 也可以直接提取模块内的成员,内置模块兼容了 ESM 的提取成员方式
import { writeFileSync } from 'fs'
writeFileSync('./bar.txt', 'es module working')

// 对于第三方的 NPM 模块也可以通过 esm 加载
import _ from 'lodash'
_.camelCase('ES Module')

// 不支持,因为第三方模块都是导出默认成员
// import { camelCase } from 'lodash'
// console.log(camelCase('ES Module'))
  • ES Modules 中可以导入CommonJS 模块
  • CommonJS 中不能导入 ES Modules 模块
  • CommonJS 始终只会导出一个默认成员
  • import 不是解构导出对象

使用babel插件兼容ES Modules

yarn add @babel/node @babel/core @babel/preset-env --dev
yarn babel-node index.js --presets=@babel/preset-env
或者添加 .babelrc 文件

{
  "presets":["@babel/preset-env"]
}

perset只是一个插件集合 具体使用插件来进行转换

yarn remove @babel/preset-env
先移除preset-env
yarn add @babel/plugin-transform-modules-commonjs --dev
.babelrc 文件

{
  "plugins":["@babel/plugin-transform-modules-commonjs"]
}

webpack打包

安装 cnpm i webpack webpack-cli --dev
使用webpack

npx webpack
webpack会按照约定 将 src/index.js --> dist/main.js

如果需要定制化配置,需要在项目根目录下创建 webpack.config.js文件 它是运行在node环境下的一个js文件,所以我们呢需要按照commonJS规范去配置它

const path = require('path')

module.exports = {
  entry: './src/index.js',//打包入口
  output: {
    filename: 'bundle.js',//输出文件名
    path: path.join(__dirname, 'output'),//必须是一个绝对路径 需要载入node中的path模块获取路径
  }
}
图片.png

webpack工作模式
webpack4增加了工作模式,这种用法大大减小了工作的复杂程度。针对不同环境的几种预设配置。
指定打包模式,默认是production模式 会对代码自动进行压缩等优化。
指定生产模式

npx webpack --mode production
指定开发模式
npx webpack --mode development
指定none模式
npx webpack --mode none
运行最原始的打包 不会进行额外处理
或者在配置文件中配置

module.exports = {
  mode:'development',//工作模式
  entry: './src/index.js',//打包入口
  output: {
    filename: 'bundle.js',//输出文件名
    path: path.join(__dirname, 'output'),//必须是一个绝对路径 需要载入node中的path模块获取路径
  }
}

webpack资源模块加载
内部的loader只能处理js文件,css等其它类型文件需要借助其它loaders进行处理。
打包css文件 需要借助css-loader

cnpm i css-loader --dev
css-loader作用是将css文件转换成js的一个模块,只是转换并没有使用,此时需要安装style-loader使用
cnpm i style-loader --dev

const path = require('path')

module.exports = {
  mode: 'none',
  entry: './src/main.css',//打包入口
  output: {
    filename: 'bundle.js',//输出文件名
    path: path.join(__dirname, 'output'),//必须是一个绝对路径 需要载入node中的path模块获取路径
  },
  module: {
    //针对其它资源加载规则的一个配置
    //每个规则对象需要设置两个属性 test属性 是一个正则表达式用来匹配打包过程遇到的文件路径  use属性 用来指定匹配的文件使用的loader
    rules: [
      {
        test: /.css$/,
        use: ['style-loader', 'css-loader']//多个loader从后往前执行
      }
    ]
  }
}

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

webpack文件资源加载器
引入一些资源,如图片,字体等
借助于file-loader

cnpm i file-loader --dev

file-loader相当于对资源文件进行拷贝至目标目录,然后将文件路径输出,我们就可以通过路径访问到资源。
//webpack.config.js
const path = require('path')

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: 'file-loader'
      }
    ]
  }
}

**webpack URL加载器
Data URLs 与 url-loader

Data URLs是一种特殊的url协议,它可以用来直接表示一个文件,当前url可以直接表示文件内容的形式。


图片.png

就不用发送http请求去请求文件资源。
可以使用url形式表示任何资源文件
借助于 url-loader

cnpm i url-loader --dev

  • 小文件使用 Data URLs ,减少请求次数
  • 大文件采取 file-loader 方式,单独提取存放,提高加载速度
rules:[
      {
        test:/.css$/,
        use:['style-loader','css-loader']
      },
      {
        test:/.png$/,
        use:{
          loader:'url-loader',
          options:{
            limit:10 * 1024 //10kb大小使用url-loader处理,超出的使用file-loader
          }
        }
      }
    ]

该方式依赖于file-loader

webpack常用加载器分类

  • 编译转换类
    将加载到的资源模块,转换为js代码 如:css-loader
  • 文件操作类型
    将加载到的资源拷贝至输出目录,同时导出文件访问路径 如:file-loader
  • 代码检查类
    代码校验,统一代码风格,提升代码质量

webpack处理ES2015
因为完成模块打包工作,所以webpack会处理import与export,它并不可以转换其它的es6特性。
需要借助于编译loader, babel-loader

cnpm i babel-loader @babel/core @babel/preset-env --dev

module:{
    rules:[
      {
        test:/.js$/,
        use:{
          loader:'babel-loader',
          options:{
            presets:['@babel/preset-env']
          }
        }
      },
      {
        test:/.css$/,
        use:['style-loader','css-loader']
      },
      {
        test:/.png$/,
        use:{
          loader:'url-loader',
          options:{
            limit:10 * 1024 //10kb大小使用url-loader处理,超出的使用file-loader
          }
        }
      }
    ]
  }
  • webpack只是一个打包工具
  • 加载器可以用来编译转换代码

webpack 模块加载方式

  • 遵循ES Modules 标准的 import 声明
  • 遵循 CommonJS 标准的 require 函数
    (对于esmodules默认导出,需要使用default)


    图片.png
  • 遵循 AMD 标准的 define 函数和 require 函数

Loader加载的非 JavaScript 也会触发资源加载
如:样式代码中的 @important 指令和 ur 函数
html中默认img src属性会触发资源加载,需要给其它属性触发资源加载对html-loader进行配置。

{
        test:/.html$/,
        use:{
          loader:'html-loader',
          options:{
            attrs:['img:src','a:href']
          }
        }
      }

webpack 插件机制
增强webpack自动化能力
Loader实现资源模块加载,从而实现整体项目的打包。plugin 解决除了资源加载其它自动化工作。
比如:自动清除dist目录,拷贝不需要打包的资源文件。
webpack 常用插件

  • 自动清除输出目录插件
    clean-webpack-plugin

cnpm i clean-webpack-plugin --dev

//webpack.config.js
const path = require('path')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
  mode: 'none',
  entry: './src/main.js',//打包入口
  output: {
    filename: 'bundle.js',//输出文件名
    path: path.join(__dirname, 'output'),//必须是一个绝对路径 需要载入node中的path模块获取路径
  },
  plugins: [
    new CleanWebpackPlugin()
  ]
}
  • 自动生成使用bundle.js的HTML
    通过Webpack输出HTML文件
    html-webpack-plugin

cnpm i html-webpack-plugin --dev

//webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin') //默认导出的就是,不需要解构
plugins: [
  new HtmlWebpackPlugin ({
      title:'webpack plugin',//设置html标题
      meta:{//设置页面标签
        viewport:'width=device-width'
      },
      template:'./src/index.html'//指定所使用的模板文件
})
]

npx webpack后 dist目录下就会生成一个index.html文件

生成多个页面

const path = require('path')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
  mode: 'none',
  entry: './src/main.js',//打包入口
  output: {
    filename: 'bundle.js',//输出文件名
    path: path.join(__dirname, 'dist'),//必须是一个绝对路径 需要载入node中的path模块获取路径
  },
  module: {
    rules: [
      {
        test: /.md$/,
        use: './markdown-loader'
      }
    ]
  },
  plugins: [
    new CleanWebpackPlugin(),
    //用于生成 index.html
    new HtmlWebpackPlugin({
      title: 'webpack plugin',//设置html标题
      meta: {//设置页面标签
        viewport: 'width=device-width'
      },
      template: './src/index.html'//指定所使用的模板文件
    }),
    //生成多个html  生成about.html
    new HtmlWebpackPlugin({
      filename: 'about.html'
    })
  ]
}

copy-webpack-plugin
在项目中 一些静态资源不需要处理,只需要将它们复制到打包后的目录中,使用该插件

cnpm i copy-webpack-plugin --dev
在webpack.config.js中导入
const CopyWebpackPlugin = require('copy-webpack-plugin')

plugins:[
//传入数组指定拷贝目录 
  new CopyWebpackPlugin ([
    'public'
  ])
]

webpack插件 Plugin 通过钩子机制实现
webpack插件要求是一个函数或者是一个包含apply方法的对象
移除打包后注释插件

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

class MyPlugin {
  //会在webpack启动时自动去调用
  //清除打包后bundle.js中的注释
  apply(compiler) {
    //emit钩子在生成文件将要写入dist文件时
    compiler.hooks.emit.tap('MyPlugin', compilation => {
      //compilation 可以理解为此次打包的上下文 大伯啊结果会放在这个对象中
      //compilation.assets所有打包文件 key:打包文件名称 compilation.assets[name].source()方法拿到文件内容
      for (const name in compilation.assets) {
        if (name.endsWith('.js')) {
          const contents = compilation.assets[name].source()
          const withoutComments = contents.replace(/\/\*\*+\*\//g, '')
          //覆盖原来值
          compilation.assets[name] = {
            source: () => withoutComments,
            size: () => withoutComments.length//webpack内部要求必须的方法
          }
        }
      }

    })//传入两个参数 1.插件名称 2.需要挂载的函数
  }
}

module.exports = {
  mode: 'none',
  entry: './src/main.js',//打包入口
  output: {
    filename: 'bundle.js',//输出文件名
    path: path.join(__dirname, 'dist'),//必须是一个绝对路径 需要载入node中的path模块获取路径
  },
  module: {
    rules: [
      {
        test: /.md$/,
        use: './markdown-loader'
      }
    ]
  },
  plugins: [
    new CleanWebpackPlugin(),
    //用于生成 index.html
    new HtmlWebpackPlugin({
      title: 'webpack plugin',//设置html标题
      meta: {//设置页面标签
        viewport: 'width=device-width'
      },
      template: './src/index.html'//指定所使用的模板文件
    }),
    //生成多个html  生成about.html
    new HtmlWebpackPlugin({
      filename: 'about.html'
    }),
    //自定义插件 移除注释插件
    new MyPlugin()
  ]
}

通过在生命周期的钩子中挂在函数实现扩展

webpack增强开发体验

实现自动编译
使用watch模式,监听文件变化,自动重新打包
在运行webpack时加上wacth

npx webpack --watch
此时webpack会以监视模式运行
实现自动刷新页面
BrowserSync 实现自动刷新功能
browser-sync dist --files "*/"
监听dist目录下文件的变化,此时浏览器会自动刷新

同时使用两个工具,操作麻烦。webpack打包不断将文件写入磁盘,browser-sync不断读磁盘内容。效率降低。

Webpack Dev Server

  • 提供用于开发的HTTP Server
  • 集成 自动编译 和 自动刷新浏览器 等功能

cnpm i webpack-dev-server --dev
为了提升效率,并没有将打包文件写入文件中,暂时存放在内存当中
npx webpack-dev-server
npx webpack-dev-server --open
自动唤起浏览器打开

  • Dev Server 默认只会 serve 打包输出文件
    只要是通过webpack输出的文件都可以被直接访问,其它静态资源文件也需要serve,需要额外去告诉webpack-dev-server
//webpack.config.js
module.exports = {
  devServer:{
    //contentBase属性指定额外的资源路径
    //可以是字符串或者数组
    contentBase:'./public'
  },
}

Webpack Dev Server 代理API服务

devServer:{
    //contentBase属性指定额外的资源路径
    //可以是字符串或者数组
    contentBase:'./public',
    proxy:{
      '/api':{
        target:'https://XXXXXXXX',
        pathRewrite:{
          '^/api':''
        },
        //不使用 localhost:8080 作为请求的主机名 会以代理的地址作为请求地址
        changeOrigin:true
      }
    }
  },

webpack 配置 Source Map
webpack.config.js

devtool:'source-map'

devtool的各个模式

  • eval-是否使用eval执行模块代码
  • cheap-Source Map 是否包含行信息
  • module-是否能够得到Loader处理之前的源代码
    开发环境选择模式
    cheap-module-eval-source-map
    (代码经过Loader转换后差异较大,所以使用module。重写打包的速度较快)
    生产模式
    ‘none’
    不生成SourceMap
    Source Map会暴露源代码
    或者选择 nosources-source-map 出错误可以定位到位置 但是不会暴漏代码

Webpack自动刷新问题

自动刷新导致页面操作状态丢失
希望在页面不刷新的前提下,模块也可以及时更新
HMR (Hot Module Replacement)模块热替换
在应用程序运行的过程中实时替换某个模块,应用运行状态不受影响
热替换只将修改的模块实时替换至应用中,不必完全刷新页面

HMR集成在webpack-dev-server中

webpack-dev-server --hot 开启
也可以在配置文件中配置开启

//webpack.config.js
const webpack = require('webpack')
module.exports = {
  devServer:{
    hot:true
  },
plugins:[
  new webpack.HotModuleReplacementPlugin()
]
}

配置完成 通过 npx webpack-dev-server --open 启动服务,修改css可以实现热更新,但是修改js还是会刷新页面。
因为Webpack中的HMR并不可以开箱即用
Webpack中的HMR需要手动处理模块热替换逻辑
因为在style-loader中自动处理了样式的热更新,因为js导出的类型与使用是不确定的,所以没有任何规律,所以无法开箱即用一种方式去实现热更新。而css就是将样式文件替换就可以实现热更新。而在使用框架开发时,可以实现热更新,是因为在框架下开发,每种文件都是有规律的。通过脚手架创建的项目内部都集成了HMR方案。

//main.js
import createEditor from './editor'
import background from './better.png'
module.hot.accept('./editor', () => {})
//通过module.hot.accept手动处理热更新 1.依赖的模块 2.处理函数
//手动处理了 就不会自动刷新页面了
module.hot.accept('./better.png', () => {
    img.src = background
    console.log(background)
  })

在手动处理时,如果处理中代码错误,会自动刷新页面。此时错误信息也就无法看到,为了解决这个问题可以配置

//webpack.config.js
module.exports = {
  devServer:{
    //hot:true
    hotOnly:true
  }
}

Webpack不同环境下的配置

  1. 配置文件根据环境不同导出不同的配置
//支持导出一个函数
//参数1.通过cli传递的一个环境参数 argv运行cli过程中所传递的所有参数
module.exports = (env,argv) => {
  const config = {}//开发环境配置

  //判断环境是否是生产环境
  if(env === 'production'){
    config.mode = 'production'
    config.devtool = false
    config.plugins = [
      ...config.plugins,
      new CleanWebpackPlugin(),//添加生产环境所需额外插件
      new CopyWebpackPlugin(['public'])
    ]
  }

  return config
}
  1. 一个环境对应一个配置文件
    大型项目使用不同环境对应不同配置文件
    一般创建三个文件 一个存放生产与开发环境的公共配置 一个为开发环境配置 一个为生产环境配置


    图片.png

    webpack.common.js

const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  entry: './src/main.js',
  output: {
    filename: 'js/bundle.js'
  },
  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'
    })
  ]
}

webpack.dev.js

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

module.exports = merge(common, {
  mode: 'development',
  devtool: 'cheap-eval-module-source-map',
  devServer: {
    hot: true,
    contentBase: 'public'
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin()
  ]
})

webpack.prod.js

const merge = require('webpack-merge') //webpack提供合并webpack  需要先安装
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const common = require('./webpack.common')

module.exports = merge(common, {
  mode: 'production',
  plugins: [
    new CleanWebpackPlugin(),
    new CopyWebpackPlugin(['public'])
  ]
})

此时没有了默认的配置文件 运行时需要指定配置文件

npx webpack --config webpack.prod.js
yarn webpack --config webpack.prod.js

DefinePlugin
为代码注入全局成员
在production模式下默认启用,为代码注入 process.env.NODE_ENV 根据该成员判断当前运行环境

//webpack.config.js
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
去除代码中未引用的部分
在生产模式下自动开启

optimization:{
  usedExports:true,//只导出用到的模块 标记
  minimize:true//压缩代码 移除
}

合并模块
concatenateModules

optimization:{
  concatenateModules:true,//将所有模块全部合并在一起 输出在一个函数中,提升运行效率 减少代码体积
  usedExports:true,//只导出用到的模块 标记
  minimize:true//压缩代码 移除
}

Tree-shaking & babel
Tree-shaking的实现前提是 ES Modules ,由Webpack打包的代码必须使用 ESM

sideEffects
通过配置标识代码是否有副作用,为Tree-shaking提供更大的压缩空间
副作用:模块执行时除了导出成员之外所作的事情
sideEffects 一般用于 npm 包标记是否有副作用

//webpack.config.js
optimization:{
  sideEffects:true,//在production模式下自动开启
}
//package.json
"sideEffects":false //标识在代码中没有副作用
代码分包 Code Splitting

项目中所有代码最终都被打包在一起,模块复杂多 bundle体积就会很大,在应用工作时,并不是每个模块都要加载的。
分包,按需去加载模块

  • 多入口打包 输出多个打包结果
  • 动态导入 ESM实现模块按需加载
多入口打包

适用于传统的多页应用程序
一个页面对应一个打包入口,公共部分单独提取

//webpack.config.js
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  mode: 'none',
  //打包多个文件,将entry定义为一个对象
  entry: {
    index: './src/index.js',//文件名:文件路径
    album: './src/album.js'
  },
  output: {
    filename: '[name].bundle.js'//动态输出文件名称
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader',
          'css-loader'
        ]
      }
    ]
  },
  plugins: [
    new CleanWebpackPlugin(),
    //HtmlWebpackPlugin会输出一个自动引入所有打包结果的html
    //要指定html所使用的文件 使用chunk 每个打包入口是一个独立的chunk
    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']
    })
  ]
}

提取公共模块
在webpack.config.js配置optimization优化属性

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

按需加载
需要用到某个模块时,再加载这个模块

  • 动态导入
    动态导入的模块会被自动分包
    通过import()导入模块
    魔法注释
    默认通过动态导入的模块,打包后文件名称默认只是一个序号,如果需要给bundle命名,使用webpack特有的魔法注释实现。
    相同名字的模块会被打包在一起。
 import(/* webpackChunkName: 'magic' */'./posts/posts').then(({ default: posts }) => {
    
  })
//打包后js
magic.bundle.js

MiniCssExtractPlugin
可以将css代码从打包结果当中提取出来的插件,可以实现css模块的按需加载

cnpm i mini-css-extract-plugin --dev

const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')

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

OptimizeCssAssetsWebpackPlugin
压缩输出的CSS文件
webpack自己的压缩只针对于js文件,其它文件压缩需要借助于其它的插件

cnpm i optimize-css-assets-webpack-plugin --dev

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') //cnpm i terser-webpack-plugin --dev 手动添加内部js压缩

module.exports = {
  mode: 'none',
  entry: {
    main: './src/index.js'
  },
  output: {
    filename: '[name].bundle.js'
  },
  optimization: {
  //使用minimizer,webpack默认为要自定义压缩,不再会实现默认js压缩
  //在需要压缩的场景下才会触发压缩,如以生产模式打包 npx webpack --mode production。
    minimizer: [
      new TerserWebpackPlugin(),//手动添加内部js压缩
      new OptimizeCssAssetsWebpackPlugin()
    ]
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          // 'style-loader', // 将样式通过 style 标签注入
          MiniCssExtractPlugin.loader,//通过MiniCssExtractPlugin.loader实现样式文件通过link标签引入
          'css-loader'
        ]
      }
    ]
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title: 'Dynamic import',
      template: './src/index.html',
      filename: 'index.html'
    }),
    new MiniCssExtractPlugin()
  ]
}

输出文件名Hash
生成前端文件都会启用服务器静态资源缓存,可以缓存文件,减少请求,提升响应速度。
应用重新发布更新,文件名不变,缓存不能及时响应更新。
在生产模式下,文件名使用Hash值,一旦资源发生改变,文件名称也随之改变。对于客户端而言,文件名称改变相当于不同的资源,会重新请求最新的资源文件。

  • 针对整个项目的hash
output:{
  filename:'[name]-[hash].bundle.js'
}

文件发生变,重新打包hash值都会发生变化

  • chunk hash
    同一路打包的hash是一样的 ,同一个chunk


    图片.png
  • contenthash
    文件级别的hash,根据输出文件内容输出hash值,不同的文件就有不同的hash值.
    解决缓存最好的方式,精确到了每一个文件
output: {
    filename: '[name]-[contenthash].bundle.js' //指定hash长度 [contenthash:8]
  },

Rollup

Rollup是一个打包工具,Rollup与webpack非常类似,相比与webpack更为小巧。
仅仅是一款 ESM 打包器,并没有其它额外的功能
Rollup中并不支持类似 HMR 这种高级特性

提供一个充分利用ESM各项特性的高效打包器
cnpm i rollup --dev
文件输入路径 及打包后文件格式 iife自调用函数格式
npx rollup ./src/index.js --format iife
将打包结果输出到文件中
npx rollup ./src/index.js --format iife --file dist/bundle.js

打包结果代码十分简洁,只会保留用到的部分,会默认开始treeshaking优化结果。

Rollup配置文件
创建rollup.config.js文件
rollup会额外处理该文件,所以可以直接使用esmodule格式

rollup.config.js

export default {
  input: 'src/index.js',//打包入口文件路径
  output: {
    file: 'dist/bundle.js',//输出文件名
    format: 'iife'//输出格式
  }
}

默认不会使用配置文件 需要加上--config参数

npx rollup --config
或者指定不同配置文件名称,区分开发生产不同的配置文件
npx rollup --config rollup.config.js

Rollup插件
插件是Rollup唯一的扩展途径

cnpm i rollup-plugin-json --dev

import json from 'rollup-plugin-json'//返回调用函数

export default {
  input: 'src/index.js',
  output: {
    file: 'dist/bundle.js',
    format: 'iife'
  },
  plugins: [
    json() //将调用结果放入plugins数组中
  ]
}

Rollup加载npm模块
并不能像webpack一样直接导入模块名称的方式导入
rollup-plugin-node-resolve
该插件可以实现直接导入模块名称的方式导入

cnpm i rollup-plugin-node-resolve --dev

import json from 'rollup-plugin-json'
import resolve from 'rollup-plugin-node-resolve'

export default {
  input: 'src/index.js',
  output: {
    file: 'dist/bundle.js',
    format: 'iife'
  },
  plugins: [
    json(),
    resolve()
  ]
}
//例如代码导入lodash-es
import _ from 'lodash-es'

Rollup 加载 CommonJs模块
rollup只涉及处理esmodule模块打包,CommonJs默认不被支持
可以使用 rollup-plugin-commonjs

cnpm i rollup-plugin-commonjs --dev

import json from 'rollup-plugin-json'
import resolve from 'rollup-plugin-node-resolve'
import commonjs from 'rollup-plugin-commonjs'

export default {
  input: 'src/index.js',
  output: {
    file: 'dist/bundle.js',
    format: 'iife'
  },
  plugins: [
    json(),
    resolve(),
    commonjs()
  ]
}

此时就可以导入处理commonJs模块

Rollup代码分包
通过动态导入方式

import('./logger').then(({ log }) => {
  log('code splitting~')
})

rollup.config.js

//此时输出为多个文件 需要使用dir属性
//通过AMD方式打包
export default {
  input: 'src/index.js',
  output: {
    // file: 'dist/bundle.js',
    // format: 'iife'
    dir: 'dist',
    format: 'amd'
  }
}

npx rollup --config

Rollup多入口打包

export default {
  // input: ['src/index.js', 'src/album.js'],
  input: {
    foo: 'src/index.js',
    bar: 'src/album.js'
  },
  output: {
    dir: 'dist',
    format: 'amd'
  }
}

打包后的js,页面不能直接去引用

<body>
  <!-- AMD 标准格式的输出 bundle 不能直接引用 -->
  <!-- <script src="foo.js"></script> -->
  <!-- 需要 Require.js 这样的库 -->
  <script src="https://unpkg.com/requirejs@2.3.6/require.js" data-main="foo.js"></script>
</body>

Rollup与 Webpack

Rollup
优点

  • 输出结果更加扁平
  • 自动移除未引用代码
  • 打包结果可读
    缺点
  • 加载非 ESM 的第三方模块比较复杂
  • 模块最终被打包在一个函数中,无法实现 HMR
  • 浏览器环境中,代码差分功能依赖 AMD 库

应用开发使用Webpack
库/框架开发 使用 Rollup

Parcel 零配置的前端应用打包器

cnpm init
cnpm i parcel-bundler --dev
创建打包入口文件 src/index.html
npx parcel src/index.html
通过parcel找到入口文件所引入的文件,进行打包

npx parcel build src/index.html
以生产模式进行打包

  • 完全零配置,自动安装依赖等特点
  • 构建速度快

ESLint

  • 主流的js lint工具 监测JS代码质量
  • 统一开发者编码风格

cnpm init -y
cnpm i eslint --save-dev
npx eslint .\file.js
使用eslint检查文件
要先将eslint初始化配置
npx eslint --init

.eslintrc.js文件

module.exports = {
    "env": {//标记运行环境
        "browser": true,//代码运行在浏览器环境中 可以使用document等成员变量
        "es2021": true
    },
    //继承共享配置
    "extends": [
        "standard"
    ],
    //设置语法解析器 是否允许es版本语法 如let const等
    "parserOptions": {
        "ecmaVersion": 12,
        "sourceType": "module"
    },
    //添加规则 属性名是内置的规则名称 属性值有三种情况 off,warn,error
    "rules": {
    }
};

配置注释

//忽略代码行
const test1 = "${name} code" // eslint-disable-line
//忽略指定规则
const test2 = "${name} code" // eslint-disable-line no-template-curly-in-string

style-lint
对css代码进行校验检测

cnpm i stylelint -D
创建stylelintrc.js配置文件,配置与eslint基本相同
安装stylelint共享配置模块
cnpm i stylelint-config-standard

//stylelintrc.js
module.exports={
  extends:"stylelint-config-standard"
}

npx stylelint ./index.css
检验指定css文件 可以通过--fix自动修复

对于sass文件检验

cnpm i stylelint-config-sass-guidelines -D

//stylelintrc.js
module.exports={
  extends:[
    "stylelint-config-standard",
    "stylelint-config-sass-guidelines"
  ]
}

npx stylelint ./index.sass

Prettier 前端代码格式化工具

cnpm i prettier -D
npx prettier ./index.css
命令格式化代码 会直接输出在控制台当中
npx prettier ./index.css --write
会将格式化后的代码写入文件
npx prettier . --write
格式化所有文件

GitHooks
通过Git Hooks在代码提交前强制lint

  • Git Hooks 是Git钩子,每个钩子都对应一个任务
  • 通过shell脚本可以编写钩子任务触发时要具体执行的操作
    在.git文件目录中


    图片.png

    这个钩子,当执行commit时都会触发这个钩子

Husky可以实现Git Hooks 的使用需求
在不使用shell脚本情况下,也可以实现使用Git Hooks钩子功能

cnpm i husky -D

//package.json
{
  "scripts": {
    "lint": "eslint ./index.js"
  },
  "husky":{
  //配置钩子
    "hooks":{
        "pre-commit":"npm run lint"
      }
  }
}

git add ,
git commit -m "lint"
此时就会对代码进行lint检验,但是校验完后不会继续提交等其它操作。借助lint-staged

cnpm i lint-staged -d

//package.json
{
  "scripts": {
    "lint": "eslint ./index.js",
    "precommit":"lint-staged"        
  },
  "husky":{
  //配置钩子
    "hooks":{
        "pre-commit":"npm run precommit"
      }
  },
  "lint-staged":{
    //定义多个执行任务
    "*.js":[
      "eslint",
      "git add"
    ]
  }
}

webpack一个配置模板

webpack.common.js

const HtmlWebpackPlugin = require('html-webpack-plugin')
const { VueLoaderPlugin } = require('vue-loader')

const utils = require('./utils.js')

module.exports = {
entry:utils.resolve('./src/main.js'),
output:{
  path:utils.resolve('./dist'),
  filename:'[name].[hash:6].js'
},

  resolve: {
    extensions: ['.js', '.vue', '.json'],
    alias: {
      'assets': utils.resolve('assets'),
      'pages': utils.resolve('src/pages'),
      'public': utils.resolve('public'),
      'components': utils.resolve('src/components')
    }
  },

  module: {
    rules: [
      {
        test: /\.(js|vue)$/,
        use: 'eslint-loader',
        enforce: 'pre'
      }
    ]
  },

  plugins: [
    new HtmlWebpackPlugin({
        title:"my vue",
      filename: 'index.html',
      template: 'src/index.html',
      inject: true,
      url:'public/'
    }),
    new VueLoaderPlugin()
  ]
}

webpack.dev.js

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

const PORT = 8080

module.exports = merge(baseConfig, {
  mode: 'development',
  devtool: 'cheap-module-eval-source-map',
  devServer: {
    clientLogLevel: 'warning',
    hot: true,
    port: PORT,
    open: true,
    contentBase:utils.resolve('./dist'),
    publicPath:'/',
    overlay: { warnings: false, errors: true },
  },

  module: {
    rules: [
      {
        test: /\.css?$/,
        use: ['vue-style-loader','css-loader']
      },
      {
        test: /\.styl(us)?$/,
        use: ['vue-style-loader','css-loader', 'stylus-loader']
      },
      {
        test: /\.(js|vue)$/,
        use: 'eslint-loader',
        enforce: 'pre'
      }, {
        test: /\.less?$/,
        use: [
          'vue-style-loader',
          'css-loader',
          'less-loader'
        ]
      } ,{
        test: /\.vue$/,
        use: 'vue-loader'
      }, {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader'
        }
      }, {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        use: {
          loader: 'url-loader',
          options: {
            limit: 10*1024,
            esModule: false,
          }
        }
      }, {
        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
        use: {
          loader: 'url-loader',
          options: {
            limit: 10000,
          }
        }
      }, {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        use: {
          loader: 'url-loader',
          options: {
            limit: 10000,
          }
        }
      }
    ]
  },

  plugins: [
    new webpack.HotModuleReplacementPlugin()
  ]
})

webpack.prod.js

const merge = require('webpack-merge')
const baseConfig = require('./webpack.common')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const {CleanWebpackPlugin} = require('clean-webpack-plugin')
const utils = require('./utils.js')

module.exports = merge(baseConfig, {
  mode: 'production',
  devtool: 'none',
  optimization: {
    usedExports:true,
    minimize:true,
    splitChunks: {
      cacheGroups: {
        commons: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendor',
          chunks: 'all'
        }
      }
    }
  },
  module: {
    rules: [
      {
        test: /\.css?$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader']
      },
      {
        test: /\.styl(us)?$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader', 'stylus-loader']
      },
      {
        test: /\.(js|vue)$/,
        use: 'eslint-loader',
        enforce: 'pre'
      }, {
        test: /\.less?$/,
        use: [
          'vue-style-loader',
          'css-loader',
          'less-loader'
        ]
      } ,{
        test: /\.vue$/,
        use: 'vue-loader'
      }, {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          
        }
      }, {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        use: {
          loader: 'url-loader',
          options: {
            limit: 10*1024,
            esModule: false,
            name: utils.assetsPath('img/[name].[hash:7].[ext]')
          }
        }
      }, {
        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
        use: {
          loader: 'url-loader',
          options: {
            limit: 10000,
            name: utils.assetsPath('media/[name].[hash:7].[ext]')
          }
        }
      }, {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        use: {
          loader: 'url-loader',
          options: {
            limit: 10000,
            name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
          }
        }
      }
    ]
  },
  plugins: [
    new CleanWebpackPlugin(),
    new MiniCssExtractPlugin({
      filename: 'main.css'
    }),
    new CopyWebpackPlugin({
      patterns: [
        {
          from: utils.resolve('public/'),
          to: utils.resolve('dist/public'),
          toType: 'dir'
        }
      ]
    })
  ]
})

util.js

const path = require('path')

module.exports = {
  resolve: function (dir) {
    return path.join(__dirname, dir)
  },

  assetsPath: function (_path) {
    const assetsSubDirectory = 'public'
    return path.posix.join(assetsSubDirectory, _path)
  }
}

资料来源:拉勾教育-前端训练营

相关文章

  • 模块化开发

    js模块化开发vue模块化开发

  • 前端开发——模块化(css模块化开发)

    掌握模块化开发的思想是我们进行模块化开发的基础。他有以下几部分组成: 模块化开发的优势 css模块化 css模块化...

  • 关于前端模块化开发

    关于前端模块化开发 1 前端中有哪些模块化开发的规范以及实现方案 2 模块化的开发的好处 3 CommonJS

  • Android 模块化开发

    一、模块化浅谈 1. Android 模块化开发介绍; 模块化开发思路就是:单独开发每个模块,用集成的方式把他们组...

  • 认识Webpack

    要了解Webpack,首先要知道前端模块化开发的概念 前端模块化 模块化的目的是使代码可以重用,模块化在任何开发中...

  • 阿里ARouter路由实现模块化开发流程

    阿里ARouter路由实现模块化开发流程(一)阿里ARouter路由实现模块化开发流程(二)

  • 模块化开发

    1、模块化开发 1.什么是模块化开发? 简单的说模块就是实现特定功能的一组方法。 模块化开发其实就是让JavaSc...

  • 笔记七:模块化开发与规范化标准

    模块化开发 当下最重要的前段开发范式,“模块化”是一种思想 模块化演变过程 早期在没有工具和规范的情况下,对模块化...

  • 一个iOS模块化开发解决方案

    一个iOS模块化开发解决方案 一个iOS模块化开发解决方案

  • 45.CommonJS详解

    什么是模块化? 到底什么是模块化、模块化开发呢? 事实上模块化开发最终的目的是将程序划分成一个个小的结构; 这个结...

网友评论

      本文标题:模块化开发

      本文链接:https://www.haomeiwen.com/subject/flaizktx.html