大前端【2-2笔记】模块化开发

一、模块化概述

模块化是一种思想。模块化是主流的代码组织方式,通过把复杂代码按照功能的不同划分为不同的模块,单独进行维护的方式提高开发效率,降低维护成本。

二、模块化演变过程

Stage1-文件划分方式

将每个功能及状态数据单独存放到不同的文件中,约定每个文件都是独立的模块,使用的时候就是将模块引入到页面中,一个script标签对应一个模块,然后再代码中直接调用全局成员(变量或函数)

缺点:

  • 污染全局作用域
  • 命名冲突

Stage2-命名空间方式

每个模块只暴露一个全局对象,所有的模块成员都挂载到对象下

缺点:

  • 无私有空间
  • 模块内部成员可以被访问修改
  • 模块之间存在依赖关系

Stage3-IIFE

  • 使用立即执行函数包裹代码,要输出的遍历挂载到一个全局对象上
  • 变量拥有了私有空间,只有通过闭包修改和访问变量
  • 参数作为依赖声明去使用,使得每个模块的依赖关系变得明显

三、模块化规范

1、CommentJs规范

NodeJs提出的一种标准,以同步模式加载模块

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

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

Require.js

实现了这个规范,是模块加载器

定义一个模块

// 定义一个模块
define('module1', ['jquery', './module2'], function ($, module2) {
  return {
    start: function () {
      $('body').animate({ margin: '200px' })
      module2()
    }
  }
})

载入模块

require(['./module1'], function (module1) {
  module1.start()
})

目前绝大多数第三方库都支持AMD规范,但是AMD使用起来相对复杂,模块JS文件请求频繁,会消耗性能

3、Sea.js + CMD(Common Module Definition)通用模块规范

4. ES Module

// 通过给script添加type=module属性,就可以使用ES Module的标准执行其中的JS代码
<script type="module">
    console.log(123)
</script>

// ./modulejs
const foo = 'es modules'
export { foo }

// ./app.js
import { foo } from './module.js'
console.log(foo) // => es modules
  • 自动采用严格模式,忽略’use strict’
  • 每个ESM模块都是单独的私有作用域
  • ESM是通过CORS去请求外部JS模块的
  • ESM的script标签会延迟脚本执行

ES6在语言标准的层面上,实现了模块功能,成为浏览器和服务器通用的模块解决方案

  • 每一个模块只加载一次, 每一个JS只执行一次, 如果下次再去加载同目录下同文件,直接从内存中读取;
  • 每一个模块内声明的变量都是局部变量, 不会污染全局作用域;
  • 模块内部的变量或者函数可以通过export导出;
  • 一个模块可以导入别的模块

模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能

一个模块就是一个独立的文件,该文件内部的所有变量,外部无法获取,只可以通过export关键字导出。

//变量
let moduleName = 'hello'
//函数
let f1 = function (msg) {
    console.log(msg)
}
export var name2 = 3
export { moduleName, f1 }
import {moduleName as a,f1,name2} from './module.js'
console.log(a) //可以使用別名
f1("hello function")
setTimeout(()=>{
   console.log(a) //输出的是hi,是修改过的值
},1300)
a = '234'  //导出之后不可进行修改

使用别名

//module.js
let moduleName = 'hello'
export { moduleName }

//app.js
import {moduleName as a } from './module.js'
console.log(a) // hello

类的导出

//module.js
export class Point{
    constructor(x, y) {
        this.x = x;
        this.y = y;
      }
    
      toString() {
        return '(' + this.x + ', ' + this.y + ')';
      }
}

//app.js
import {Point} from './module.js'
let points = new Point(1,3)
console.log(points) // Point {x: 1, y: 3}

export default { }

//module.js
let num1 = 1
let num2 = 2
// default后是一个对象
export default {num1,num2}

//app.js
import module from './module.js'
console.log(module.num1)

注意事项

  1. export { } 是一个固定用法,并非导出了一个对象
  2. 导出的值是引用关系,是只读成员,不可进行修改
  3. 导入的时候必须使用完整的名称,不可省略扩展名

导入用法

  1. 导入时,可以使用绝对路径,相对路径如果是同目录必须使用‘./’,不可省略

  2. 只加载不提取,可以使用:import module.js或者 import {} from module.js

  3. 全部导入,使用:import * as mod from module.js

  4. import 关键词只能出现在最外层的作用域

  5. 动态加载

    import('./module.js').then(function(module){
        console.log(module)
    })
    

ESModules中可以导入CommentJs模块

commentJs不可以导入ESModule模块

CommentJs始终只导出一个默认成员

四、Webpack打包(Module bundle)

​ ES Module存在环境兼容问题,通过模块化方式划分的模块较多,网络请求频繁,在前端应用开发中不仅仅需要JavaScript代码需要模块化,随着应用的日益复杂,html,css同样也面临相同的问题,也就是说,所有的前端资源都需要模块化。

​ 所需要的工具需要满足的条件:

  • 新特性代码编译
  • 模块化JavaScript打包
  • 支持不同文件类型的资源模块

打包工具:解决前端整体的模块化,并不单指JavaScript模块化

1、Webpack快速上手

具体过程如下

  • 创建package.json,执行命令:yarn init --yes

  • 安装webpack:yarn add webpack webpack-cli --dev

  • 查看webpack版本:yarn webpack --version

  • 打包:执行yarn webpack,此时会生成一个dis文件夹,文件中有main.js

  • 修改html的js引入路径,并去掉type=module

  • 也可以在script中定义:

    "scripts": {

    "build":"webpack"

    },

    后期只使用:yarn build即可

// heading.js
export default () => {
    const element = document.createElement('h2')
    element.textContent = 'Hello World!'
    element.addEventListener('click',()=>{
        alert('hello webpack')  
    })
    return element
}

//index.js
import createHeading from './heading.js'
const heading = createHeading()
document.body.append(heading)
 <!--  index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <!-- <script src="src/index.js" type="module"></script> -->
    <script src="dist/main.js"></script>
</body>
</html>

2、Webpack配置文件

webpack.config.js是运行在nodeJs中的一个文件,需要根据commonJs的规范编写代码,文件导出一个对象,通过导出的对象属性完成相应的配置选项

const path = require('path')

module.exports = {
    //入口,打包webapck的入口文件地址,相对路径时,./不能省略
    entry: './src/main.js',
    //output:指定输出路径,filename:文件名称
    output:{
        filename:'bundle.js' ,
        //path是绝对路径
        path: path.join(__dirname,"output")
    }
}

3、Webpack工作模式

运行yarn webpack,会报错

WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/

意思是没有指定工作模式,默认以生产模式进行打包,有三种工作模式:

  • production:生产模式会默认启动优化,优化我们的打包结果
  • development:开发模式,会自动优化打包的速度,添加一些调试过程中的辅助到代码中
  • none:原始状态的打包,不会做任何处理

两种方式:

1、通过yarn webpack --mode 模式名称来执行.

2、在配置文件中指定:

mode: "none"

4、Webpack资源模块加载

直接打包css文件

ERROR in ./src/main.css 1:4
Module parse failed: Unexpected token (1:4)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
> body{
|     margin: 0;
|     padding:20px;
error Command failed with exit code 2.

需要安裝相应的loader

yarn add css-loader --dev
yarn add style-loader --dev

修改配置:

 entry: './src/main.css',
 module:{
        rules:[
            {
                test: /.css$/,
                //注意:执行顺序从右向左
                use:[
                    //style-loader是将css-loader处理后的结果,通过style的形式追加到页面上
                    'style-loader',
                    'css-loader'
                ]
            }
        ]
    }

5、webpack 导入资源模块

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AUYKUMZa-1595861755410)(C:\Users\张艳杰\AppData\Roaming\Typora\typora-user-images\1595166953909.png)]

webpack的入口文件可以是js,css的类型文件,但由于前端项目是由JS驱动,所以webpack将入口文件设置为JS文件,需要用到CSS时,直接在JS文件中通过import导入,import './main.css

webpack建议根据代码需要在JS中动态导入资源文件,因为需要资源的不是应用,而是代码。因为是JavaScript驱动了整个前端应用,这样做的好处是逻辑合理,JS确实需要这些资源文件,确保上线资源不缺失,都是必要的

6、webpack 文件资源加载器

图片,字体等其他资源文件,这些是没有办法通过js的方式进行表示,对于这些文件,我们就需要使用文件资源加载器file loader,安装命令:yarn add file-loader --dev

//引入图片文件,返回值为路径,
import icon from './icon.jpg'
//创建image
const img = new Image()
//设置路径
img.src = icon
//追加
document.body.append(img)

webpack.config.js中的rules添加

 {
       test: /.jpg$/,
       use:'file-loader'
  }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T0FBBqPN-1595861755414)(C:\Users\张艳杰\AppData\Roaming\Typora\typora-user-images\1595178956006.png)]

文件加载器工作过程:

webpack在打包时遇到图片文件,然后根据配置文件中的配置匹配到对应的文件加载器,此时文件加载器开始工作,先将导入的文件拷贝输出到指定的目录,将拷贝完之后的路径作为返回值返回,然后资源就被发布,可在浏览器看到。

8、webpack 常用加载器分类

  1. 编译转换类,转换为JS代码,如css-loader
  2. 文件操作类,将资源文件拷贝到输出目录,将文件访问路径向外导出,如:file-loader
  3. 代码检查器,统一代码风格,提高代码质量,如:es-loader

9、webpack 处理ES2015

webpack只是打包工具,不会处理ES6或者其他新特性,所以可以使用加载器来编译转化代码,babel-loaderbabel-loader依赖于babel的核心模块,@babel/core@babel/preset-env

yarn add babel-loader @babel/core @babel/preset-env --dev
{
   test: /.js$/,
   use: {
     loader: 'babel-loader',
     options: {
       presets: ['@babel/preset-env']
     },
     exclude: /(node_modules)/ //一定不能省略,否则会出错
   }
 }

10、webpack的模块加载方式

1、遵循ES Modules标准的import声明

//引入图片文件,返回值为路径,
import icon from './icon.jpg'
//创建image
const img = new Image()
//设置路径
img.src = icon
//追加
document.body.append(img)

2、遵循CommonJS标准的require函数。对于ES的默认导出,要通过require(’./XXX’).default的形式获取

const createHeading = require('./heading.js').default
const icon = require('./icon.png')

3、循AMD标准的define函数和require函数

4、Loader加载的非JavaScript也会触发资源加载

@import url(reset.css);

body{
    margin: 0;
    background-color: #582;
    color: red;
    background-image: url('./icon.jpg');
    background-size: cover;
}

css-loader在处理css代码时,遇到了background属性中的url函数,发现是引入的资源文件是png格式的文件,则将这个资源文件 交给url-loader处理

对于html文件,也会引入资源文件,例如img的src,

// footer.html
<footer>
    <!-- <img src="./icon.jpg" width="250"> -->
    <a href='./icon.jpg'>look</a>
</footer>

// main.js
import footerHtml from './footer.html'
document.write(footerHtml)

//配置
{
                test: /.html$/,
                use:{
                    loader:'html-loader',
                    options:{
                        attributes:{
                        list: [
                            {
                              tag: 'img',
                              attribute: 'src',
                              type: 'src'
                            },
                            {
                              tag: 'a',
                              attribute: 'href',
                              type: 'src'
                            }
                          ]
                        }
                    }
                }
            }

5、遵循CommonJS标准的require函数。对于ES的默认导出,要通过require(’./XXX’).default的形式获取,遵循AMD标准的define函数和require函数

6、Loader加载的非JavaScript也会触发资源加载

11、webpack核心工作原理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lRCIwNwK-1595861755415)(C:\Users\张艳杰\AppData\Roaming\Typora\typora-user-images\1595345719081.png)]

12、webpack开发一个loader

开发一个markdowm-loader

// markdown-loader.js
const marked = require('marked')

module.exports = source => {
    // console.log(source)
    // return 'console.log("hello")'
    const html = marked(source)
    // return `module.export = ${JSON.stringify(html)}`
    // return `export default  ${JSON.stringify(html)} `

    //返回html,字符串交给下一个loader处理
    return html
}

about.md

# hello
qiuqiu

配置:

 module: {
    rules: [
      {
        test: /.md$/,
        use: ['html-loader', './markdown-loader.js']
      }
    ]
  }

其实loader就相当于是一个管道,就是一个从输入到输出之间的一个转换。

13、Webpack插件机制

(1)插件机制是Webpack当中一个核心特性,目的是增强webpack在项目自动化方面的能力。

(2)loader就是负责实现我们项目当中各种各样资源模块加载。从而去实现整体项目的打包。

(3)plugin则是用来去解决项目中除了资源加载以外其他的一些自动化工作。例如,可以帮我们去实现,自动在打包之前去清除DIV的目录。

(1)自动在打包之前去清除dist目录

安装:

yarn add clean-webpack-plugin --dev

webpack.config.js

const {CleanWebpackPlugin} = require('clean-webpack-plugin')
plugins: [
  new CleanWebpackPlugin()
]
(2)自动生成HTML插件

安装:

yarn add html-webpack-plugin --dev

webpack.config.js

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

plugins:[
        new CleanWebpackPlugin(),
        new HtmlWebpackPlugin()
]

(3)根据模板生成页面

new HtmlWebpackPlugin({
  title: 'Webpack Plugin Sample',
  meta: {
    viewport: 'width=device-width'
  }
})

重新yarn webpack,查看index.html会看到数据动态更改

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Webpack Plugin Sample</title>
  <meta name="viewport" content="width=device-width"></head>
  <body>
  <script src="dist/bundle.js"></script></body>
</html>

(4)生成多个html

 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'
    })
  ]

(5)拷贝那些不需要参与打包的资源文件到输出目录

安装:yarn add copy-webpack-plugin --dev

const CopyWebpackPlugin = require('copy-webpack-plugin')

new CopyWebpackPlugin({
  patterns: ['public']
})

14、webpack开发插件

相比于Loader,Plugin拥有更宽的能力范围,Plugin通过钩子机制实现。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZCsot6hN-1595861755417)(C:\Users\张艳杰\AppData\Roaming\Typora\typora-user-images\1595435864700.png)]

Webpack要求插件必须是一个函数或者是一个包含apply方法的对象。

自定义插件,MyPlugin

class Myplugin {
    apply(compiler) {
        console.log("qidong")
        compiler.hooks.emit.tap('Myplugin', 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
                    }
                }
            }
        })
    }
}

使用:

plugins:[
  new Myplugin()  
]

15、webpack开发体验问题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zhWoY8K2-1595861755418)(C:\Users\张艳杰\AppData\Roaming\Typora\typora-user-images\1595436709975.png)]

按照上图的方式进行,显得很原始,为了提高开发效率,我们做出设想

  • 以HTTP Server运行,而不是进行文件预览
  • 自动编译构建+自动刷新,可以减少开发中重复的操作
  • 支持sourceMap

16、自动编译

watch工作模式:监听文件变化,自动重新打包,我们只需专心编码,不用重复打包

yarn webpack --watch //启动命令时添加参数 --watch

16、自动刷新浏览器

安装BrowserSync: yarn add browser-sync

启动: browser-sync dist --files '**/*'

17、webpack Dev server

提供开发服务器,集成“自动编译”,“自动刷新浏览器”

安装:yarn add webpack-dev-server --dev

运行:yarn webpack-dev-server --open

静态资源访问:

通過webpack打包输出的文件都可以被访问到,其他静态资源也需要serve,需要额外的告诉webserver

 devServer:{
     contentBase: './public'
 }

contentBase额外为开发服务器指定查找资源目录

18、webpack Dev server代理API

使用CORS解决跨域问题的前提是API要支持CORS

webpack Dev server支持代理配置

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': ''
                },
                // 不能使用localhost:8080作为请求GitHub的主机名
                changeOrigin: true, // 以实际代理的主机名去请求
              }
        }
    },

19、Source Map介绍

通过构建编译的操作我们可以将开发阶段的源代码转化为可以在开发环境运行的代码,这是一种进步,但是,这就会导致:运行代码和源代码之间有很大的差异,这种情况下,如果我们需要调试应用,将无从下手。SourceMap很好的解决了这个问题。

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

20、Webpack配置SourceMap

在webpack.config.js中配置:devtool:'source-map',然后运行yarn webpack,查看生成的文件,会看到生成了bundle.js.map,并且在bundle.js末尾有://# sourceMappingURL=bundle.js.map

Webpack 支持sourceMap 12种不同的方式,每种方式的速度和效果各不相同。效果最好的速度最慢,速度最快的一般没有什么效果

21、Webpack eval 模式的Source Map

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GuPkUh7i-1595861755419)(C:\Users\张艳杰\AppData\Roaming\Typora\typora-user-images\1595519053306.png)]

配置:

devtool:'eval'

打包以后错误信息只有源代码文件名称,没有具体的行列信息,因为在打包的时候,已经分成了各个模块,并且将不同的模块输出到不同的文件中。

22、WebPack devtool 模式对比与选择

  • eval- 是否使用eval执行代码模块
  • cheap- Source map是否包含行信息
  • module-是否能够得到Loader处理之前的源代码
  • inline- SourceMap 不是物理文件,而是以URL形式嵌入到代码中
  • hidden- 看不到SourceMap文件,但确实是生成了该文件
  • nosources- 没有源代码,但是有行列信息。保护源代码不暴露

开发模式推荐使用:eval-cheap-module-source-map,原因有:

  • 代码每行不会太长,可以没有列
  • 代码经过Loader转换后的差异较大
  • 首次打包速度慢无所谓,重新打包相对较快

生产模式推荐使用:none,原因有:

  • Source Map会暴露源代码
  • 调试是开发阶段的事情
  • 对代码实在没有信心可以使用nosources-source-map

23、自动刷新,webpack HMR

当检测到页面变化则重新编译上传,会导致页面刷新的问题

HMR(Hot Module Replacement) 模块热替换,应用运行过程中,实时替换某个模块,应用运行状态不受影响。

HMR是webpack中最强大的功能之一,极大程度的提高了开发者的工作效率。

HMR已经集成在了webpack-dev-server中,运行webpack-dev-server --hot,也可以通过配置文件开启.

配置:

const webpack =  require('webpack')

 devServer:{
        hot:true
        }
plugins:[
   new webpack.HotModuleReplacementPlugin()
]

HMR并不是对所有文件开箱即用,

Q:为什么样式文件的热更新开箱即用?答:因为样式文件通过loader处理,在style-loader中已经做了热更新处理,所以不用额外操作

24、webpack生产环境优化

webpack建议我们为不同的环境创建不同的配置,

(1)配置文件根据环境不同导出不同配置

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

class Myplugin {
    apply(compiler) {
        console.log("qidong")
        compiler.hooks.emit.tap('Myplugin', 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
                    }
                }
            }
        })
    }
}

module.exports = (env, argv) => {
    const config = {
        //指定打包模式
        mode: 'none',
        //入口,打包webapck的入口文件地址,相对路径时,./不能省略
        entry: './src/main.js',
        //output:指定输出路径,filename:文件名称
        output: {
            filename: 'bundle.js',
            //path是绝对路径
            path: path.join(__dirname, "dist"),
            publicPath: 'dist/'
        },
        devtool: 'source-map',
        devServer: {
            contentBase: './public',
            hot: true,
            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': ''
                    },
                    // 不能使用localhost:8080作为请求GitHub的主机名
                    changeOrigin: true, // 以实际代理的主机名去请求
                }
            }
        },
        module: {
            rules: [
                {
                    test: /.css$/,
                    use: [
                        'style-loader',
                        'css-loader'
                    ]
                },
                {
                    test: /.jpg$/,
                    use: 'file-loader'
                },
                {
                    test: /.png$/,
                    use: {
                        loader: 'url-loader',
                        options: {
                            limit: 10 * 1024
                        }
                    }
                },
                {
                    test: /.md$/,
                    // use: './markdown-loader'
                    use: [
                        'html-loader',
                        './markdown-loader'
                    ]
                },
                {
                    test: /.js$/,
                    // use:'babel-loader'
                    use: {
                        loader: 'babel-loader',
                        options: {
                            presets: ['@babel/preset-env']
                        }
                    },
                    exclude: /(node_modules)/ //一定不能省略,否则会出错
                },
                {
                    test: /.html$/,
                    use: {
                        loader: 'html-loader',
                        options: {
                            attributes: {
                                list: [
                                    {
                                        tag: 'img',
                                        attribute: 'src',
                                        type: 'src'
                                    },
                                    {
                                        tag: 'a',
                                        attribute: 'href',
                                        type: 'src'
                                    }
                                ]
                            }
                        }
                    }
                },
            ]
        },
        plugins: [
            new CleanWebpackPlugin(),
            new HtmlWebpackPlugin({
                title: 'Webpack Plugin Sample',
                meta: {
                    viewport: 'width=device-width'
                },
                template: './src/index.html'
            }),
            // 用于生成about.html 
            new HtmlWebpackPlugin({
                filename: 'about.html'
            }),
            //开发期间最好不要使用
            // new CopyWebpackPlugin({
            //     patterns: ['public']
            // }),
            //new Myplugin()
            new webpack.HotModuleReplacementPlugin()
        ]
    }
    //判断
    if (env === 'production') {
        config.mode = 'production'
        config.devtool = false
        config.plugins = [
            ...config.plugins,
            new CleanWebpackPlugin(),
            new CopyWebpackPlugin({
                patterns: ['public']
            })
        ]
    }
    return config
}
//yarn webpack --env production

2、不同环境对应不同配置文件

​ const {merge} = require(‘webpack-merge’)

创建三个文件,webpack.dev.js,webpack.prod.js,webpack.common.js

webpack.dev.js:

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

module.exports = merge(common, {
    mode:'development',
 }
)

webpack.prod.js:

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

module.export = merge(common, {
  mode: 'production',
})

webpack.common.js:

const path = require('path')
const { rule } = require('postcss')
const webpack = require('webpack')

module.exports = {
    entry: './src/main.js',
    //output:指定输出路径,filename:文件名称
    output: {
        filename: 'bundle.js',
        //path是绝对路径
        path: path.join(__dirname, "dist"),
        publicPath: 'dist/'
    },
    devtool: 'source-map',
    devServer: { 
        contentBase: './public',
        hot: true,
        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': ''
                },
                // 不能使用localhost:8080作为请求GitHub的主机名
                changeOrigin: true, // 以实际代理的主机名去请求
            }
        }
    },
    module: {
        rules: [
            {
                test: /.css$/,
                use: [
                    'style-loader',
                    'css-loader'
                ]
            },
            {
                test: /.jpg$/,
                use: 'file-loader'
            },
            {
                test: /.png$/,
                use: {
                    loader: 'url-loader',
                    options: {
                        limit: 10 * 1024
                    }
                }
            },
            {
                test: /.md$/,
                // use: './markdown-loader'
                use: [
                    'html-loader',
                    './markdown-loader'
                ]
            },
            {
                test: /.js$/,
                // use:'babel-loader'
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env']
                    }
                },
                exclude: /(node_modules)/ //一定不能省略,否则会出错
            },
            {
                test: /.html$/,
                use: {
                    loader: 'html-loader',
                    options: {
                        attributes: {
                            list: [
                                {
                                    tag: 'img',
                                    attribute: 'src',
                                    type: 'src'
                                },
                                {
                                    tag: 'a',
                                    attribute: 'href',
                                    type: 'src'
                                }
                            ]
                        }
                    }
                }
            },
        ]
    }
}

25、webpack DefinePlugin

为代码注入全局成员,在production下,插件默认启动起来,并且注入一个全局变量process.env.NODE_ENV

const webpack = require('webpack')

module.exports = {
    mode:'none',
    entry:'./src/main.js',
    output:{
        filename:'bundle.js',
    },
    plugins:[
        new webpack.DefinePlugin({
            // API_BASE_URL:"123"
            API_BASE_URL:'"123"'
        })
    ]
}

26、webpack体验Tree-Shaking

Tree-Shaking在生产模式下自动开启,可以摇掉代码中未引用到的代码(dead-code),Tree-Shaking并不是webpack中的某一个配置选项,是一组功能搭配使用后的效果。

使用

optimization: {
    usedExports: true,
    minimize: true,
    concatenateModules: true
  }

如果把代码看作是一棵大树,则usedExports负责标记枯树叶,minimize负责摇掉它们

注意

纠正使用Babel-Loader,会导致Tree-Shaking失效的问题:因为Tree-Shaking前提是ES Modules,由Webpack打包的代码必须使用ESM,为了转化ES中的新特性,会使用babel处理新特性,就有可能将ESM转化CommonJS,而我们使用的@babel/preset-env这个插件集合就会转化ESM为CommonJS,所以Tree-Shaking会不生效。但是在最新版babel-loader关闭了转换ESM的插件,所以使用babel-loader不会导致Tree-Shaking失效

27、sideEffects副作用

webpack4.0中新增的新特性 - sideEffects 副作用,允许我们通过配置的方式标识代码是否有副作用,副作用指

的是模块执行时除了导出成员之外所做的事情。如果没有副作用,则没有用到的模块则不会被打包。

配置方法:

(1)在webpack.config.js中配置开启副作用功能

 optimization: {
        //开启功能
        sideEffects:true,
        usedExports: true,
        //是否压缩bundle.js文件
        //minimize: true
 }

(2)在package.json中标识代码没有副作用

 "sideEffects":false

使用sideEffects的前提是确保你的代码真的没有副作用,否则在webpack打包时就会误删掉有副作用的代码。比如在原型上添加方法,则这些方法就是副作用,则此时应该忽略掉,配置如下:

"sideEffects": [
  "./src/extend.js",
  "*.css"
]

28、webpack代码分割

目前导报是所有的代码都会被打包到一起,如果打包文件过多,bundle会非常大。而并不是每个模块在启动时都是必要的,所以需要分包、按需加载。资源太大了不行,太碎了太零散了也不行。太大了会影响加载速度;太碎了会导致请求次数过多,因为在目前主流的HTTP1.1有很多缺陷,如同域并行请求限制、每次请求都会有一定的延迟,请求的Header浪费带宽流量。所以模块打包时有必要的。

(1)多入口打包

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

配置文件:webpack.config.js

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

module.exports = {
    mode:'none',
    entry:{
       index: './src/index.js',
       album: './src/album.js'
    },
    output:{
        filename:'[name].bundle.js'
    },
    module:{
        rules:[
            {
                test:/\.css$/,
                use:[
                    //style-loader是将css-loader处理后的结果,通过style的形式追加到页面上
                    'style-loader',
                    'css-loader'
                ]
            }
        ]
    },
    plugins:[
        new CleanWebpackPlugin(),
        new HtmlWebpackPlugin({
            filename:'index.html',
            chunks:['index'] //引入的js文件
        }),
        new HtmlWebpackPlugin({
            filename:'album.html',
            chunks:['album'] //引入的js文件
        }),
    ]

}

提取公共部分

如果某一个文件被多个文件引用,则可以使用配置的方式对公共部分进行提取。配置如下:

optimization: {
        splitChunks: {
            chunks: 'all'
        }
 }
(2)动态导入

需要用到某个模块时,再加载这个模块,动态导入的模块会被自动分包。通过动态导入生成的文件只是一个序号,可以使用魔法注释指定分包产生bundle的名称。相同的chunk名会被打包到一起。

import('./post/posts').then({default: posts}) => {
  mainElement.appendChild(posts())
}

魔法注释,分包产生的文件名是数字,如果想自定义名称,则可以通过魔法注释的方式进行,如果注释的文件名一致,则会打包到同一个文件中。

import(/* webpackChunkName: 'component' */'./post/posts').then({default: posts}) => {
  mainElement.appendChild(posts())
}

29、MiniCssExtractPlugin

提取css到单个文件,通过这个插件可以实现css的按需加载。

安装:mini-css-extract-plugin

使用:

const MiniCssExtracPlugin = require('mini-css-extract-plugin')

 plugins: [
        new MiniCssExtracPlugin()
 ]
 
 module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    //style-loader是将css-loader处理后的结果,通过style的形式追加到页面上
                    //'style-loader',
                    MiniCssExtracPlugin.loader,
                    'css-loader'
                ]
            }
        ]
 }

30、OptimizeCssAssetsWebpackPlugin

webpack支持对js文件的压缩,但是对其他为文件没有作用,所以我们需要使用插件对其进行压缩。

安装: yarn add optimize-css-assets-webpack-plugin

安装:yarn add terser-webpack-plugin --dev

webpack.config.js

const OptimizeCssAssetWebpackPlugin = require('optimize-css-assets-webpack-plugin')


 optimization: {
        minimizer: [
          new OptimizeCssAssetWebpackPlugin()
        ]
      },

plugins: [
        new OptimizeCssAssetWebpackPlugin()
    ]

31、webpack输出文件名hash

三种方式:

1、hash

new MiniCssExtracPlugin({
      filename:'[name]-[hash].bundle.css'
}),

2、chunkhash

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

3、contenhash

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

指定长度: hash:number,number=8推荐使用

 output: {
        filename: '[name]-[chunkhash:8].bundle.js'
},

五、Rollup打包

rollup是Esmodule的打包器,与webpack作用很类似,相对于webpack,rullup更小巧,仅仅是一款ESM打包器,Rollup并不支持类似HMR这种高级特性。Rollup并不是要与webpack竞争,初衷只是希望提供一个充分利用ESM各项特性的高效打包器。

1、快速上手

三个文件,logger.js,message.js,index.js

logger.js

export const log = msg => {
    console.log('=======INFO==========')
    console.log(msg)
    console.log('=====================')
}

export const error = msg => {
    console.log('=======ERROR==========')
    console.log(msg)
    console.log('=====================') 
}

message.js

export default {
    hi: 'hey,i am qiuqiu'
}

index.js

//导入模块成员
import { log } from './logger'
import messages from './messages'

const hi = messages.hi
console.log(hi)

执行打包命令

// 安装
yarn add rollup --dev 
//打包,将文件输出到dist目录下的bundle.js文件中
yarn rollup ./src/index.js --format iife --file dist/bundle.js

打包以后的bundle.js

(function () {
    'use strict';

    var messages = {
        hi: 'hey,i am qiuqiu'
    };

    //导入模块成员

    const hi = messages.hi;
    console.log(hi);

}());

2、Rollup配置文件

我们可以使用配置文件的方式对rollup进行配置,配置如下:

// rollup.config.js
export default  {
    //入口文件
   input:'./src/index.js',
   output:{
       file:'dist/bundle.js',
       format:'iife'
   }
}

也可以创建多个配置文件,在执行打包命令的时候指定相应的打包文件

打包命令yarn rollup --config,指定打包文件:yarn rollup --config rollup.config.js

3、Rollup使用插件

Rollup自身的功能就是对ESM进行合并打包,如果项目有需要更高级的需求,如加载其他类型资源模块,导入CommonJS模块,编译ES新特性,对于这些高级的需求,Rollup支持使用插件的方式扩展实现,插件是Rollup唯一的扩展方式。

安装json插件: yarn add rollup-plugin-json --dev

使用插件:

rollup.config.json

import json from 'rollup-plugin-json'

export default  {
   input:'./src/index.js',
   output:{
       file:'dist/bundle.js',
       format:'iife'
   },
   plugins:[
       //执行的结果
       json()
   ]
}

index.js

//导入模块成员
import { log } from './logger'
import messages from './messages'
import {name,version} from '../package.json'

const hi = messages.hi
console.log(hi)
console.log(name,version)

bundle.js

(function () {
    'use strict';

    var messages = {
        hi: 'hey,i am qiuqiu'
    };

    var name = "rollup-demo1";
    var version = "1.0.0";

    //导入模块成员

    const hi = messages.hi;
    console.log(hi);
    console.log(name,version);

}());

4、Rollup加载npm

安装:yarn add rollup-plugin-node-resolve --dev,yarn add lodash-es --dev

rollup-resolve-node-plugin,通过这个插件我们可以根据模块名称直接导入相应的模块

配置文件rollup.json.js

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()
   ]
}

打包入口文件index.js

//导入模块成员
import _ from 'lodash-es'
import { log } from './logger'
import messages from './messages'
import {name,version} from '../package.json'

const hi = messages.hi
console.log(hi)
console.log(name,version)
console.log(_.camelCase('hello world'))

打包結果:

...
  var name = "rollup-demo1";
  var version = "1.0.0";

  //导入模块成员

  const hi = messages.hi;
  console.log(hi);
  console.log(name,version);
  console.log(lodash.camelCase('hello world'));
...

5、加载CommonJS模块

rollup只处理ESM的模块打包,如果我们在代码中导入commonJs模块,默认是hi不被支持的,但是目前还是有很多npm模块使用commonJs导出模块成员,为了兼容这些模块,官方为我们提供了rollup-plugin-commonJs插件。

安装:yarn add rollup-plugin-commonjs

配置文件rollup.config.js

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()
   ]
}

index.js

//导入模块成员
import cjs from './cjs-module'

console.log(cjs)

cjs-module.js

module.exports = {
    foo:'foo'
}

bundle.js

  ...
  var cjsModule = {
      foo:'foo'
  };

  //导入模块成员
  console.log(cjsModule);
  ...

5、代码拆分

在最新版本中,rollup已经支持代码拆分,我们可以使用动态导入的方法实现按需加载,rollup内部也处理代码的拆分,也就是分包。

index.js

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

rollup.config.js

output:{
    dir:'dist',
    format:'amd'
},

logger打包以后的文件内容

define(['exports'], function (exports) { 'use strict';

    const log = msg => {
        console.log('=======INFO==========');
        console.log(msg);
        console.log('=====================');
    };

    const error = msg => {
        console.log('=======ERROR==========');
        console.log(msg);
        console.log('====================='); 
    };

    exports.error = error;
    exports.log = log;

});

6、多入口打包

rullup支持多文件打包

album.js

import { log } from './logger'
console.log(123)
const hi = log("456")
console.log(hi,123)

index.js

import { log } from './logger'
console.log(123)
const hi = log("123")
console.log(hi,456)

webpack.config.js

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

7、Rollup/webpack选用

Rollup

优点

  • 输出结果更加扁平化,执行效率更高
  • 自动移除为使用的代码
  • 打包结果依旧可读

缺点

  • 加载非ESM的第三方模块比较复杂,需要配置一大堆插件
  • 模块最终被打包到一个函数中,无法实现HMR热开发
  • 浏览器环境中,代码拆分功能依赖AMD库

如果我们正在开发应用程序,需要大量引入第三方插件,需要分包,则选择webpack

如果我们正在开发一个框架或者类库,则使用rollup打包 ,但也不是绝对的标准

六、Parcel

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

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值