模塊化開發(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模塊獲取路徑
}
}
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)容的形式。
就不用發(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)境下的配置
- 配置文件根據(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
}
-
一個環(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)練營