乐趣区

关于javascript:模块化

1、模块化演变过程

立刻执行函数

2、commonjs 标准

一个文件就是一个模块
每个模块都有点独自的作用域
通过 module.exports 到处
通过 require 导入

commonjs 是以同步模式加载模块

node 没问题然而浏览器段有问题

所以就要应用 amd 标准,require.js

// 因为 jQuery 中定义的是一个名为 jquery 的 AMD 模块
// 所以应用时必须通过 'jquery' 这个名称获取这个模块
// 然而 jQuery.js 并不在同级目录下,所以须要指定门路
define('module1', ['jquery', './module2'], function ($, module2) {
  return {start: function () {$('body').animate({margin: '200px'})
      module2()}
  }
})

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

应用起来较为简单,然而生态比拟好,模块划分过于粗疏的话 js 文件申请频繁

3、模块化标准规范

浏览器:ES Modules

node:CommonJS

4、ES Modules 根本个性


  <!-- 通过给 script 增加 type = module 的属性,就能够以 ES Module 的规范执行其中的 JS 代码了 -->
  <script type="module">
    console.log('this is es module')
  </script>


  <!-- 1. ESM 主动采纳严格模式,疏忽 'use strict' -->
  <script type="module">
    console.log(this)
  </script>


  <!-- 2. 每个 ES Module 都是运行在独自的公有作用域中 -->
  <script type="module">
    var foo = 100
    console.log(foo)
  </script>
  <script type="module">
    console.log(foo)
  </script>


  <!-- 3. ESM 是通过 CORS 的形式申请内部 JS 模块的 -->
  <!-- <script type="module" src="https://unpkg.com/jquery@3.4.1/dist/jquery.min.js"></script> -->


  <!-- 4. ESM 的 script 标签会提早执行脚本 -->
  <script defer src="demo.js"></script>
  <p> 须要显示的内容 </p>

5、ESmodule 导出


 var obj = {name, age}


export default {name, age}
// 导出一个对象,属性为 name,age

export {name, age}


// export name // 谬误的用法


// export 'foo' // 同样谬误的用法


setTimeout(function () {name = 'ben'}, 1000)
// 援用方也会在一秒钟之后更改

6、import


// import {name} from 'module.js'
// import {name} from './module.js'
// import {name} from '/04-import/module.js'
// import {name} from 'http://localhost:3000/04-import/module.js'


// var modulePath = './module.js'
// import {name} from modulePath
// console.log(name)


// if (true) {//   import { name} from './module.js'
// }

动静导入,间接援用地址
// import('./module.js').then(function (module) {//   console.log(module)
// })



// 命名成员和默认成员都要导入进去
// import {name, age, default as title} from './module.js'
import abc, {name, age} from './module.js'
console.log(name, age, abc)

7、


  <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>

加上 nomodule 就能够实现不反对 esmodule 的浏览器动静加载这个脚本

8、

ES Modules 能够导入 Common JS 模块
CommonJs 不能导入 ES Modules 模块
CommonJS 始终导出一个默认成员
留神 import 不是解构导出对象

9、打包的由来

ES Modules 的弊病:

模块化须要解决兼容

模块化的网络申请太频繁,因为每一个文件都要从服务端

所有前端资源除了 JS 之外都是须要模块化的

打包工具的性能:

编译 JS 新个性

生产阶段须要打包为一个 js 文件

反对不同品种的资源类型

10、模块打包工具

webpack:

模块打包器

兼容性解决:loader

代码拆分

资源模块:容许应用 js 引入其余资源

11、webpack 疾速上手

12、webpack 运行原理

bundle 这个文件是一个立刻执行函数

传入参数就是代码里的每一个模块

有一个对象来贮存加载过的模块,

会依照进入程序加载模块,

13、资源模块加载

不同的资源文件须要配置不同的 loader 加载器

在 rules 外面配置


rules:[
{
  test:/.css$/,
  use:[
    'style-loader',‘css-loader'
  ]
}]


14、webpack 导入资源模块

个别还是以 js 文件为入口

那么其余资源的文件

例如在 js 文件引入 css 文件

将整个我的项目变成了 js 驱动的我的项目

15、webpack 文件资源加载器

装置 file-loader 加载器

rules:[
{
  test:/.png$/,
  use:[‘file-loader',‘css-loader'
  ]
}]

output:{puclicPath:’dist/‘根目录文件,这样图片能力找到那个地址}

16、URL 加载器

use:{
  Loader:url-loader,
  options:{Limit: 10 * 1024// 低于这个大小才用 url-loader}
}]

小文件用 url-loader

17、js 转换

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

18、模块加载形式

遵循 ES

遵循 Common

遵循 AMD 的 define 函数和 require 函数

Loader 加载的时候也会触发

款式代码中的 @import 指令和 url 函数
@import url(reset.css);

HTML 中的 src 属性和 href 属性

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

19、loader 工作原理


markdown-loader

const marked = require('marked')


module.exports = source => {// console.log(source)
  // return 'console.log("hello ~")' 这里是因为必须返回 js 语句

  const html = marked(source)
  // return html
  // return `module.exports = "${html}"` 这样子会导致换行符等被疏忽
  // return `export default ${JSON.stringify(html)}` 转成 json 就能够防止这个问题


  // 返回 html 字符串交给下一个 loader 解决
  return html
}

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

20、插件机制

加强自动化能力,例如压缩代码

loader 是用来加强资源加载能力的

21、罕用插件

主动清理输入目录的插件


  plugins: [new webpack.ProgressPlugin(),
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin()]

依据模板动静生成,动静输入


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

22、开发一个插件

插件比起 loader 有着更宽泛的能力

实质是钩子机制,webpack 打包全流程中会提供钩子


class MyPlugin {apply (compiler) {console.log('MyPlugin 启动')


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

23、dev server


devServer: {

contentBase: './public’, 额定资源门路

    proxy: {
      '/api': {//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': '' // 正则表达式替换掉 api 字符},
        // 不能应用 localhost:8080 作为申请 GitHub 的主机名
        changeOrigin: true
      }
    }

}
},

24、source map

调试和报错的根底,源代码的地图

//# sourceMappingURL=jquery-3.4.1.min.map

这个正文能够在浏览器当中映射出源代码

25、webpack 配置

devtool: ‘source-map’,// 这个设置能够实现 source map

26、webpack eval 模式的 source map

devtool: ‘eval’,

通过 eval 函数执行,并且在结尾附上那段正文

构建速度是最快的

然而很难定位行列信息,只能定位到文件

27、不同 devtool 模式比照


module.exports = allModes.map(item => {
    return {
        devtool: item,
        mode: 'none',
        entry: './src/main.js',
        output: {filename: `js/${item}.js`
        },
        module: {
            rules: [
                {
                    test: /\.js$/,
                    use: {
                        loader: 'babel-loader',
                        options: {presets: ['@babel/preset-env']
                        }
                    }
                }
            ]
        },
        plugins: [
            new HtmlWebpackPlugin({filename: `${item}.html`
            })
        ]
    }
})
'eval',
'cheap-eval-source-map', // 阉割版的 source map,定位时定位在了源代码
'cheap-module-eval-source-map',
'eval-source-map',
'cheap-source-map',
'cheap-module-source-map',// 阉割版的 source map,定位时定位在了
'inline-cheap-source-map',
'inline-cheap-module-source-map',
'source-map',
'inline-source-map',
'hidden-source-map',
'nosources-source-map'

eval- 是否应用 eval 执行模块代码
cheap- 是否蕴含行信息
module- 是否可能失去 loader 解决之前的源代码

inline- 把 sourcemap 嵌入到代码当中
hidden – 没有 source map 的成果
nosources- 能看到错误信息,然而浏览器下面看不到

如何抉择呢?

抉择适合的 source map

开发模式中:cheap-module-eval-source-map

起因:

代码每行不超过 80 个字符
通过 loader 转换后变动较大
首次打包速度慢无所谓,从新打包较快

生产模式中:none

source map 会裸露源代码

28、主动刷新

主动刷新会导致页面状态失落

页面不刷新

29、HMR(模块热更新)

在运行过程的即时替换,利用运行状态不受影响


  plugins: [new webpack.HotModuleReplacementPlugin()
  ]
  devServer: {
    hot: true
    // hotOnly: true // 只应用 HMR,不会 fallback 到 live reloading
  },



// ============ 以下用于解决 HMR,与业务代码无关 ============
// main.js

if (module.hot) {先判断是否存在这个模块
  let lastEditor = editor
  module.hot.accept('./editor', () => {// console.log('editor 模块更新了,须要这里手动解决热替换逻辑')
    // console.log(createEditor)


    const value = lastEditor.innerHTML
    document.body.removeChild(lastEditor)
    const newEditor = createEditor()
    newEditor.innerHTML = value
    document.body.appendChild(newEditor)
    lastEditor = newEditor
  })


  module.hot.accept('./better.png', () => {
    img.src = background
    console.log(background)
  })
}

HMR 的非凡逻辑会在编译之后被清掉

30、生产环境优化

mode 的用法

不同环境下的配置

①、配置文件依据环境不同导出不同配置


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
}
    

②、一个环境对应一个配置文件


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

31、DefinePlugin

为代码注入


plugins: [

new webpack.DefinePlugin({

// 值要求的是一个代码片段
API_BASE_URL: JSON.stringify('https://api.example.com')

})
]

32、treeShaking


optimization: {

// 模块只导出被应用的成员,标记枯树枝
usedExports: true,

// 尽可能合并每一个模块到一个函数中,作用域晋升
concatenateModules: true,

// 压缩输入后果,把树枝要下来
// minimize: true
}

生产环境中会主动开启

33、Tree-shaking & Babel

必须应用 ES Modules

babel-loader


module: {

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 转换
      // 设置为 commonjs 会强制转换
          ['@babel/preset-env', { modules: 'auto'}]

       ]
     }
}
}
]
},
optimization: {

// 模块只导出被应用的成员
usedExports: true,

// 尽可能合并每一个模块到一个函数中
// concatenateModules: true,
// 压缩输入后果
// minimize: true
}

34、sideEffects

optimization: {

sideEffects: true,

}

如果一个模块里被援用进来然而只用外面的一个模块,那么其它模块就不会被引入

import {Button} from ‘./components’
//components 当中除了 Button 之外的模块都不会被引入

要确保你的代码没有副作用

// 副作用模块
import ‘./extend’

在 extend 当中为 number 原型增加了一个办法,这样就会导致这个办法是没有被引入的

解决办法

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

在 package.json 文件中

35、代码宰割

并不是每个模块都是在启动时须要的

所以须要实现按需加载

然而也不能分的太细

同域并行申请

申请头占资源

两种解决办法

①多入口打包


entry: {

index: './src/index.js',

album: './src/album.js'

}, 留神这里是对象,数组的话就是多个文件打包到一个文件

output: {filename: '[name].bundle.js’// 编译完结后会替换[name]

},







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'
    }
  },

②动静导入

按需加载



const render = () => {
  const hash = window.location.hash || '#posts'


  const mainElement = document.querySelector('.main')


  mainElement.innerHTML = ''if (hash ==='#posts') {// mainElement.appendChild(posts())
    import(/* webpackChunkName: 'components' */'./posts/posts').then(({default: posts}) => {mainElement.appendChild(posts())
    })
  } else if (hash === '#album') {// mainElement.appendChild(album())
    import(/* webpackChunkName: 'components' */'./album/album').then(({default: album}) => {mainElement.appendChild(album())
    })
  }
}


render()


window.addEventListener('hashchange', render)

36、minicss

  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          // 'style-loader', // 将款式通过 style 标签注入
          MiniCssExtractPlugin.loader,
          'css-loader'
        ]
      }
    ]
  },

37、OptimizeCssAssetsWebpackPlugin

  optimization: {
    // 配置在这里能够保障只有压缩性能开启时才会执行上面插件
    // 所以须要本人手动保障一下 js 的压缩
    minimizer: [new TerserWebpackPlugin(),
      new OptimizeCssAssetsWebpackPlugin() 压缩款式文件]
  },

38、输入文件名 hash

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

通过 hash 值管制缓存

退出移动版