模塊化開發(fā)

模塊化開發(fā)是一種思想,隨著前端項目的日益龐大。為了使我們開發(fā)協(xié)作更加高效,互不影響。將編寫的代碼模塊化,更利于協(xié)作與維護。使得開發(fā)更加高效。

CommonJs規(guī)范

  • 一個文件就是一個模塊
  • 每個模塊都有單獨的作用域
  • 通過 module.exoprts 導(dǎo)出成員
  • 通過 require 函數(shù)載入模塊

CommonJS是以同步模式加載模塊
因為node執(zhí)行機制是在啟動時加載模塊,執(zhí)行過程中不會加載模塊,這種方式在node中不會有問題。但是在瀏覽器端,使用每次頁面加載都會導(dǎo)致大量同步請求出現(xiàn)。所以當時有一個特地為瀏覽器設(shè)定的規(guī)范 AMD

AMD(Asynchronous Module Definition)異步模塊定義規(guī)范

用define定義一個模塊

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

//加載一個模塊
require(['./module'],function(module){
  module.fn()
})
  • AMD使用起來相對復(fù)雜
  • 模塊JS文件請求頻繁 頁面效率低下

模塊化標準規(guī)范

在nodeJS中 使用 CommonJS 規(guī)范
在瀏覽器環(huán)境中 使用 ESModules 規(guī)范

ES Modules

基本特性

  • ESM 自動采用嚴格模式 忽略 'use strict'
  • 每個ESM 都是運行在單獨的私有作用域中
  • ESM是通過 CORS 的方式請求外部JS模塊的 必須在http serve環(huán)境中運行
  • ESM script標簽會延遲執(zhí)行腳本 相當于添加defer屬性
  <!-- 通過 script 添加 type=module 屬性,就可以使 ES Module標準執(zhí)行js -->
<script type="module">
    console.log('sssssss')
  </script>

導(dǎo)入導(dǎo)出

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

ES Module 瀏覽器環(huán)境 Polyfill

在支持新語法的瀏覽器中 腳本執(zhí)行一次,引入polyfill也會執(zhí)行一次代碼,就會出現(xiàn)執(zhí)行兩次現(xiàn)象。此時,引入nomodule 屬性,就只會在不支持的瀏覽器中工作。(只適用于開發(fā)階段,生產(chǎn)環(huán)境還是要先進行編譯處理)

<!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 瀏覽器環(huán)境 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 加載內(nèi)置模塊了
import fs from 'fs'
fs.writeFileSync('./foo.txt', 'es module working')

// 也可以直接提取模塊內(nèi)的成員,內(nèi)置模塊兼容了 ESM 的提取成員方式
import { writeFileSync } from 'fs'
writeFileSync('./bar.txt', 'es module working')

// 對于第三方的 NPM 模塊也可以通過 esm 加載
import _ from 'lodash'
_.camelCase('ES Module')

// 不支持,因為第三方模塊都是導(dǎo)出默認成員
// import { camelCase } from 'lodash'
// console.log(camelCase('ES Module'))
  • ES Modules 中可以導(dǎo)入CommonJS 模塊
  • CommonJS 中不能導(dǎo)入 ES Modules 模塊
  • CommonJS 始終只會導(dǎo)出一個默認成員
  • import 不是解構(gòu)導(dǎo)出對象

使用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只是一個插件集合 具體使用插件來進行轉(zhuǎn)換

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

如果需要定制化配置,需要在項目根目錄下創(chuàng)建 webpack.config.js文件 它是運行在node環(huán)境下的一個js文件,所以我們呢需要按照commonJS規(guī)范去配置它

const path = require('path')

module.exports = {
  entry: './src/index.js',//打包入口
  output: {
    filename: 'bundle.js',//輸出文件名
    path: path.join(__dirname, 'output'),//必須是一個絕對路徑 需要載入node中的path模塊獲取路徑
  }
}
圖片.png

webpack工作模式
webpack4增加了工作模式,這種用法大大減小了工作的復(fù)雜程度。針對不同環(huán)境的幾種預(yù)設(shè)配置。
指定打包模式,默認是production模式 會對代碼自動進行壓縮等優(yōu)化。
指定生產(chǎn)模式

npx webpack --mode production
指定開發(fā)模式
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資源模塊加載
內(nèi)部的loader只能處理js文件,css等其它類型文件需要借助其它loaders進行處理。
打包css文件 需要借助css-loader

cnpm i css-loader --dev
css-loader作用是將css文件轉(zhuǎn)換成js的一個模塊,只是轉(zhuǎn)換并沒有使用,此時需要安裝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: {
    //針對其它資源加載規(guī)則的一個配置
    //每個規(guī)則對象需要設(shè)置兩個屬性 test屬性 是一個正則表達式用來匹配打包過程遇到的文件路徑  use屬性 用來指定匹配的文件使用的loader
    rules: [
      {
        test: /.css$/,
        use: ['style-loader', 'css-loader']//多個loader從后往前執(zhí)行
      }
    ]
  }
}

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/',//根目錄設(shè)置
  },
  module: {
    rules: [
      {
        test: /.css$/,
        use: [
          'style-loader',
          'css-loader'
        ]
      },
      {
        test: /.png$/,
        use: 'file-loader'
      }
    ]
  }
}

**webpack URL加載器
Data URLs 與 url-loader

Data URLs是一種特殊的url協(xié)議,它可以用來直接表示一個文件,當前url可以直接表示文件內(nèi)容的形式。


圖片.png

就不用發(fā)送http請求去請求文件資源。
可以使用url形式表示任何資源文件
借助于 url-loader

cnpm i url-loader --dev

  • 小文件使用 Data URLs ,減少請求次數(shù)
  • 大文件采取 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常用加載器分類

  • 編譯轉(zhuǎn)換類
    將加載到的資源模塊,轉(zhuǎn)換為js代碼 如:css-loader
  • 文件操作類型
    將加載到的資源拷貝至輸出目錄,同時導(dǎo)出文件訪問路徑 如:file-loader
  • 代碼檢查類
    代碼校驗,統(tǒng)一代碼風格,提升代碼質(zhì)量

webpack處理ES2015
因為完成模塊打包工作,所以webpack會處理import與export,它并不可以轉(zhuǎn)換其它的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只是一個打包工具
  • 加載器可以用來編譯轉(zhuǎn)換代碼

webpack 模塊加載方式

  • 遵循ES Modules 標準的 import 聲明
  • 遵循 CommonJS 標準的 require 函數(shù)
    (對于esmodules默認導(dǎo)出,需要使用default)


    圖片.png
  • 遵循 AMD 標準的 define 函數(shù)和 require 函數(shù)

Loader加載的非 JavaScript 也會觸發(fā)資源加載
如:樣式代碼中的 @important 指令和 ur 函數(shù)
html中默認img src屬性會觸發(fā)資源加載,需要給其它屬性觸發(fā)資源加載對html-loader進行配置。

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

webpack 插件機制
增強webpack自動化能力
Loader實現(xiàn)資源模塊加載,從而實現(xiàn)整體項目的打包。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') //默認導(dǎo)出的就是,不需要解構(gòu)
plugins: [
  new HtmlWebpackPlugin ({
      title:'webpack plugin',//設(shè)置html標題
      meta:{//設(shè)置頁面標簽
        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',//設(shè)置html標題
      meta: {//設(shè)置頁面標簽
        viewport: 'width=device-width'
      },
      template: './src/index.html'//指定所使用的模板文件
    }),
    //生成多個html  生成about.html
    new HtmlWebpackPlugin({
      filename: 'about.html'
    })
  ]
}

copy-webpack-plugin
在項目中 一些靜態(tài)資源不需要處理,只需要將它們復(fù)制到打包后的目錄中,使用該插件

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

plugins:[
//傳入數(shù)組指定拷貝目錄 
  new CopyWebpackPlugin ([
    'public'
  ])
]

webpack插件 Plugin 通過鉤子機制實現(xiàn)
webpack插件要求是一個函數(shù)或者是一個包含apply方法的對象
移除打包后注釋插件

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

class MyPlugin {
  //會在webpack啟動時自動去調(diào)用
  //清除打包后bundle.js中的注釋
  apply(compiler) {
    //emit鉤子在生成文件將要寫入dist文件時
    compiler.hooks.emit.tap('MyPlugin', compilation => {
      //compilation 可以理解為此次打包的上下文 大伯啊結(jié)果會放在這個對象中
      //compilation.assets所有打包文件 key:打包文件名稱 compilation.assets[name].source()方法拿到文件內(nèi)容
      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內(nèi)部要求必須的方法
          }
        }
      }

    })//傳入兩個參數(shù) 1.插件名稱 2.需要掛載的函數(shù)
  }
}

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',//設(shè)置html標題
      meta: {//設(shè)置頁面標簽
        viewport: 'width=device-width'
      },
      template: './src/index.html'//指定所使用的模板文件
    }),
    //生成多個html  生成about.html
    new HtmlWebpackPlugin({
      filename: 'about.html'
    }),
    //自定義插件 移除注釋插件
    new MyPlugin()
  ]
}

通過在生命周期的鉤子中掛在函數(shù)實現(xiàn)擴展

webpack增強開發(fā)體驗

實現(xiàn)自動編譯
使用watch模式,監(jiān)聽文件變化,自動重新打包
在運行webpack時加上wacth

npx webpack --watch
此時webpack會以監(jiān)視模式運行
實現(xiàn)自動刷新頁面
BrowserSync 實現(xiàn)自動刷新功能
browser-sync dist --files "*/"
監(jiān)聽dist目錄下文件的變化,此時瀏覽器會自動刷新

同時使用兩個工具,操作麻煩。webpack打包不斷將文件寫入磁盤,browser-sync不斷讀磁盤內(nèi)容。效率降低。

Webpack Dev Server

  • 提供用于開發(fā)的HTTP Server
  • 集成 自動編譯 和 自動刷新瀏覽器 等功能

cnpm i webpack-dev-server --dev
為了提升效率,并沒有將打包文件寫入文件中,暫時存放在內(nèi)存當中
npx webpack-dev-server
npx webpack-dev-server --open
自動喚起瀏覽器打開

  • Dev Server 默認只會 serve 打包輸出文件
    只要是通過webpack輸出的文件都可以被直接訪問,其它靜態(tài)資源文件也需要serve,需要額外去告訴webpack-dev-server
//webpack.config.js
module.exports = {
  devServer:{
    //contentBase屬性指定額外的資源路徑
    //可以是字符串或者數(shù)組
    contentBase:'./public'
  },
}

Webpack Dev Server 代理API服務(wù)

devServer:{
    //contentBase屬性指定額外的資源路徑
    //可以是字符串或者數(shù)組
    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執(zhí)行模塊代碼
  • cheap-Source Map 是否包含行信息
  • module-是否能夠得到Loader處理之前的源代碼
    開發(fā)環(huán)境選擇模式
    cheap-module-eval-source-map
    (代碼經(jīng)過Loader轉(zhuǎn)換后差異較大,所以使用module。重寫打包的速度較快)
    生產(chǎn)模式
    ‘none’
    不生成SourceMap
    Source Map會暴露源代碼
    或者選擇 nosources-source-map 出錯誤可以定位到位置 但是不會暴漏代碼

Webpack自動刷新問題

自動刷新導(dǎo)致頁面操作狀態(tài)丟失
希望在頁面不刷新的前提下,模塊也可以及時更新
HMR (Hot Module Replacement)模塊熱替換
在應(yīng)用程序運行的過程中實時替換某個模塊,應(yīng)用運行狀態(tài)不受影響
熱替換只將修改的模塊實時替換至應(yīng)用中,不必完全刷新頁面

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 啟動服務(wù),修改css可以實現(xiàn)熱更新,但是修改js還是會刷新頁面。
因為Webpack中的HMR并不可以開箱即用
Webpack中的HMR需要手動處理模塊熱替換邏輯
因為在style-loader中自動處理了樣式的熱更新,因為js導(dǎo)出的類型與使用是不確定的,所以沒有任何規(guī)律,所以無法開箱即用一種方式去實現(xiàn)熱更新。而css就是將樣式文件替換就可以實現(xiàn)熱更新。而在使用框架開發(fā)時,可以實現(xiàn)熱更新,是因為在框架下開發(fā),每種文件都是有規(guī)律的。通過腳手架創(chuàng)建的項目內(nèi)部都集成了HMR方案。

//main.js
import createEditor from './editor'
import background from './better.png'
module.hot.accept('./editor', () => {})
//通過module.hot.accept手動處理熱更新 1.依賴的模塊 2.處理函數(shù)
//手動處理了 就不會自動刷新頁面了
module.hot.accept('./better.png', () => {
    img.src = background
    console.log(background)
  })

在手動處理時,如果處理中代碼錯誤,會自動刷新頁面。此時錯誤信息也就無法看到,為了解決這個問題可以配置

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

Webpack不同環(huán)境下的配置

  1. 配置文件根據(jù)環(huán)境不同導(dǎo)出不同的配置
//支持導(dǎo)出一個函數(shù)
//參數(shù)1.通過cli傳遞的一個環(huán)境參數(shù) argv運行cli過程中所傳遞的所有參數(shù)
module.exports = (env,argv) => {
  const config = {}//開發(fā)環(huán)境配置

  //判斷環(huán)境是否是生產(chǎn)環(huán)境
  if(env === 'production'){
    config.mode = 'production'
    config.devtool = false
    config.plugins = [
      ...config.plugins,
      new CleanWebpackPlugin(),//添加生產(chǎn)環(huán)境所需額外插件
      new CopyWebpackPlugin(['public'])
    ]
  }

  return config
}
  1. 一個環(huán)境對應(yīng)一個配置文件
    大型項目使用不同環(huán)境對應(yīng)不同配置文件
    一般創(chuàng)建三個文件 一個存放生產(chǎn)與開發(fā)環(huán)境的公共配置 一個為開發(fā)環(huán)境配置 一個為生產(chǎn)環(huán)境配置


    圖片.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 根據(jù)該成員判斷當前運行環(huán)境

//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
去除代碼中未引用的部分
在生產(chǎn)模式下自動開啟

optimization:{
  usedExports:true,//只導(dǎo)出用到的模塊 標記
  minimize:true//壓縮代碼 移除
}

合并模塊
concatenateModules

optimization:{
  concatenateModules:true,//將所有模塊全部合并在一起 輸出在一個函數(shù)中,提升運行效率 減少代碼體積
  usedExports:true,//只導(dǎo)出用到的模塊 標記
  minimize:true//壓縮代碼 移除
}

Tree-shaking & babel
Tree-shaking的實現(xiàn)前提是 ES Modules ,由Webpack打包的代碼必須使用 ESM

sideEffects
通過配置標識代碼是否有副作用,為Tree-shaking提供更大的壓縮空間
副作用:模塊執(zhí)行時除了導(dǎo)出成員之外所作的事情
sideEffects 一般用于 npm 包標記是否有副作用

//webpack.config.js
optimization:{
  sideEffects:true,//在production模式下自動開啟
}
//package.json
"sideEffects":false //標識在代碼中沒有副作用
代碼分包 Code Splitting

項目中所有代碼最終都被打包在一起,模塊復(fù)雜多 bundle體積就會很大,在應(yīng)用工作時,并不是每個模塊都要加載的。
分包,按需去加載模塊

  • 多入口打包 輸出多個打包結(jié)果
  • 動態(tài)導(dǎo)入 ESM實現(xiàn)模塊按需加載
多入口打包

適用于傳統(tǒng)的多頁應(yīng)用程序
一個頁面對應(yīng)一個打包入口,公共部分單獨提取

//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'//動態(tài)輸出文件名稱
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader',
          'css-loader'
        ]
      }
    ]
  },
  plugins: [
    new CleanWebpackPlugin(),
    //HtmlWebpackPlugin會輸出一個自動引入所有打包結(jié)果的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優(yōu)化屬性

optimization: {
    splitChunks: {
      // 自動提取所有公共模塊到單獨 bundle
      chunks: 'all'
    }
  },

按需加載
需要用到某個模塊時,再加載這個模塊

  • 動態(tài)導(dǎo)入
    動態(tài)導(dǎo)入的模塊會被自動分包
    通過import()導(dǎo)入模塊
    魔法注釋
    默認通過動態(tài)導(dǎo)入的模塊,打包后文件名稱默認只是一個序號,如果需要給bundle命名,使用webpack特有的魔法注釋實現(xiàn)。
    相同名字的模塊會被打包在一起。
 import(/* webpackChunkName: 'magic' */'./posts/posts').then(({ default: posts }) => {
    
  })
//打包后js
magic.bundle.js

MiniCssExtractPlugin
可以將css代碼從打包結(jié)果當中提取出來的插件,可以實現(xiàn)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實現(xiàn)樣式文件通過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 手動添加內(nèi)部js壓縮

module.exports = {
  mode: 'none',
  entry: {
    main: './src/index.js'
  },
  output: {
    filename: '[name].bundle.js'
  },
  optimization: {
  //使用minimizer,webpack默認為要自定義壓縮,不再會實現(xiàn)默認js壓縮
  //在需要壓縮的場景下才會觸發(fā)壓縮,如以生產(chǎn)模式打包 npx webpack --mode production。
    minimizer: [
      new TerserWebpackPlugin(),//手動添加內(nèi)部js壓縮
      new OptimizeCssAssetsWebpackPlugin()
    ]
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          // 'style-loader', // 將樣式通過 style 標簽注入
          MiniCssExtractPlugin.loader,//通過MiniCssExtractPlugin.loader實現(xiàn)樣式文件通過link標簽引入
          'css-loader'
        ]
      }
    ]
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title: 'Dynamic import',
      template: './src/index.html',
      filename: 'index.html'
    }),
    new MiniCssExtractPlugin()
  ]
}

輸出文件名Hash
生成前端文件都會啟用服務(wù)器靜態(tài)資源緩存,可以緩存文件,減少請求,提升響應(yīng)速度。
應(yīng)用重新發(fā)布更新,文件名不變,緩存不能及時響應(yīng)更新。
在生產(chǎn)模式下,文件名使用Hash值,一旦資源發(fā)生改變,文件名稱也隨之改變。對于客戶端而言,文件名稱改變相當于不同的資源,會重新請求最新的資源文件。

  • 針對整個項目的hash
output:{
  filename:'[name]-[hash].bundle.js'
}

文件發(fā)生變,重新打包hash值都會發(fā)生變化

  • chunk hash
    同一路打包的hash是一樣的 ,同一個chunk


    圖片.png
  • contenthash
    文件級別的hash,根據(jù)輸出文件內(nèi)容輸出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自調(diào)用函數(shù)格式
npx rollup ./src/index.js --format iife
將打包結(jié)果輸出到文件中
npx rollup ./src/index.js --format iife --file dist/bundle.js

打包結(jié)果代碼十分簡潔,只會保留用到的部分,會默認開始treeshaking優(yōu)化結(jié)果。

Rollup配置文件
創(chuàng)建rollup.config.js文件
rollup會額外處理該文件,所以可以直接使用esmodule格式

rollup.config.js

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

默認不會使用配置文件 需要加上--config參數(shù)

npx rollup --config
或者指定不同配置文件名稱,區(qū)分開發(fā)生產(chǎn)不同的配置文件
npx rollup --config rollup.config.js

Rollup插件
插件是Rollup唯一的擴展途徑

cnpm i rollup-plugin-json --dev

import json from 'rollup-plugin-json'//返回調(diào)用函數(shù)

export default {
  input: 'src/index.js',
  output: {
    file: 'dist/bundle.js',
    format: 'iife'
  },
  plugins: [
    json() //將調(diào)用結(jié)果放入plugins數(shù)組中
  ]
}

Rollup加載npm模塊
并不能像webpack一樣直接導(dǎo)入模塊名稱的方式導(dǎo)入
rollup-plugin-node-resolve
該插件可以實現(xiàn)直接導(dǎo)入模塊名稱的方式導(dǎo)入

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()
  ]
}
//例如代碼導(dǎo)入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()
  ]
}

此時就可以導(dǎo)入處理commonJs模塊

Rollup代碼分包
通過動態(tài)導(dǎo)入方式

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
優(yōu)點

  • 輸出結(jié)果更加扁平
  • 自動移除未引用代碼
  • 打包結(jié)果可讀
    缺點
  • 加載非 ESM 的第三方模塊比較復(fù)雜
  • 模塊最終被打包在一個函數(shù)中,無法實現(xiàn) HMR
  • 瀏覽器環(huán)境中,代碼差分功能依賴 AMD 庫

應(yīng)用開發(fā)使用Webpack
庫/框架開發(fā) 使用 Rollup

Parcel 零配置的前端應(yīng)用打包器

cnpm init
cnpm i parcel-bundler --dev
創(chuàng)建打包入口文件 src/index.html
npx parcel src/index.html
通過parcel找到入口文件所引入的文件,進行打包

npx parcel build src/index.html
以生產(chǎn)模式進行打包

  • 完全零配置,自動安裝依賴等特點
  • 構(gòu)建速度快

ESLint

  • 主流的js lint工具 監(jiān)測JS代碼質(zhì)量
  • 統(tǒng)一開發(fā)者編碼風格

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

.eslintrc.js文件

module.exports = {
    "env": {//標記運行環(huán)境
        "browser": true,//代碼運行在瀏覽器環(huán)境中 可以使用document等成員變量
        "es2021": true
    },
    //繼承共享配置
    "extends": [
        "standard"
    ],
    //設(shè)置語法解析器 是否允許es版本語法 如let const等
    "parserOptions": {
        "ecmaVersion": 12,
        "sourceType": "module"
    },
    //添加規(guī)則 屬性名是內(nèi)置的規(guī)則名稱 屬性值有三種情況 off,warn,error
    "rules": {
    }
};

配置注釋

//忽略代碼行
const test1 = "${name} code" // eslint-disable-line
//忽略指定規(guī)則
const test2 = "${name} code" // eslint-disable-line no-template-curly-in-string

style-lint
對css代碼進行校驗檢測

cnpm i stylelint -D
創(chuàng)建stylelintrc.js配置文件,配置與eslint基本相同
安裝stylelint共享配置模塊
cnpm i stylelint-config-standard

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

npx stylelint ./index.css
檢驗指定css文件 可以通過--fix自動修復(fù)

對于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鉤子,每個鉤子都對應(yīng)一個任務(wù)
  • 通過shell腳本可以編寫鉤子任務(wù)觸發(fā)時要具體執(zhí)行的操作
    在.git文件目錄中


    圖片.png

    這個鉤子,當執(zhí)行commit時都會觸發(fā)這個鉤子

Husky可以實現(xiàn)Git Hooks 的使用需求
在不使用shell腳本情況下,也可以實現(xiàn)使用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"
此時就會對代碼進行l(wèi)int檢驗,但是校驗完后不會繼續(xù)提交等其它操作。借助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":{
    //定義多個執(zhí)行任務(wù)
    "*.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)
  }
}

資料來源:拉勾教育-前端訓(xùn)練營

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,362評論 6 537
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,013評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,346評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,421評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,146評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,534評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,585評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,767評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,318評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,074評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,258評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,828評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,486評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,916評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,156評論 1 290
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,993評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,234評論 2 375

推薦閱讀更多精彩內(nèi)容

  • 前端模塊化開發(fā)簡介 歷史上,JavaScript 一直沒有模塊(module)體系,無法將一個大程序拆分成互相依賴...
    榮兒飛閱讀 4,285評論 0 6
  • 1. 前言 現(xiàn)在的前端開發(fā), 通常是一個單頁面應(yīng)用,每一個視圖通過異步的方式加載,這導(dǎo)致頁面初始化和使用過程中會加...
    majun00閱讀 734評論 0 2
  • 模塊化開發(fā) 模塊化只是一種思想 模塊化演變過程 Stage 1 - 文件劃分方式將功能與數(shù)據(jù)放置到不同的文件當中約...
    彪悍de文藝青年閱讀 253評論 0 0
  • 什么是模塊化開發(fā)?js模塊化有必要嗎? 恰好這幾天我也遇到應(yīng)聘者在說自己了解模塊化編程,于是我在網(wǎng)上搜刮了些資料再...
    隨心__閱讀 2,325評論 0 2
  • 什么是模塊化開發(fā)? 前端開發(fā)中,起初只要在script標簽中嵌入幾十上百行代碼就能實現(xiàn)一些基本的交互效果,后來js...
    半世韶華憶闌珊閱讀 667評論 0 0