为什么我们要使用webpack--模块化编程
早期Webpack 刚出来的时候,是为了解决低版本浏览器不支持ESM(module)模块化的问题,将各个分散的JavaScript模块合并成一个文件,同时将多个JavaScript脚本文件合并成一个文件,减少HTTP请求的数量,有助于提升页面首次加载速度。在版本三开始更是乘胜追击,引入了Loader,Plugin机制,提供了各种个性化构建的能力(babel,css合并,代码压缩等)取代了同时代的Gulp等构建工具
CommonJS 与 ESM 的差异
除了语法上的差异,另外一个差异在于ESM导入模块的变量都是强绑定,导出模块的变量一旦发生变化,对应导入的模块也会随之变化,而CommonJS中导入的模块都是值的传递和引用传递(类似于传参)
CommonJS
// a.js
let mod = 'first value'
setTimeout (() => {
mod = 'second value'
}, 500)
module.exports = mod
// b.js
const mod = require('./b')
setTimeout (() => {
console.log (mod)
}, 1000)
node a.js
first value
ESM
//b.mjs
export let mod = 'first value'
setTimeout (() => {
mod = 'second value'
}, 500)
//b.mjs
import { mod } from './b.mjs'
setTimeout (() => {
console.log (mod)
}, 1000)a
node --experimental-modules a.mjs
second value
另外,ConmmonJS的模块实现,实际是给每一个模块文件做了一层函数包裹,从而使得每一个模块获取 require/module __filename/dirname 变量,那么实际执行过程是
// b.js
(function (exports, require, module, __filename) {
const mod = require('./a.js')
setTimeout(() => {
console.log(mod)
}, 1000)
})
而ESM的模块是通过 import/export 关键词实现,没有对应的函数包裹,所以在 ESM 模块中,需要使用 import/meta 变量来获取 __dilename/__dirname import/export是ECMScript 实现的一个包含模块元数据的特定对象,主要用于存放模块的 url ,而node中只支持加载本地模块,所以 俩都是使用 file 协议
require声明前的代码是会先执行,ESM 是最后一步再去执行
import url from 'url'
import path from 'path'
// import.meta: { url: file://Users/dev/mjs/a.mjs }
const __filename = url.fileURLToPath (import.meta.url)
const __dirname = path.dirname (__filename)
加载原理
所有的模块化开发,都是从一个入口文件开始,无论是NODE还是浏览器,都会根据这个入口文件进行检索。
步骤
- 构造,寻找并且下载所有的文件并解析成模块信息(Module Records)(包含当前模块的语法抽象树,当前模块的依赖信息)
- 实例化,将模块记录实例化各个模块质检的import,export部分对象的都在内存中指向一起
- 执行,将import export 内存指向的地址填上实际的值
不过,相信等到 Node.js 14 LTS 版本发布时,ESM 的支持应该就能进入稳定阶段了
vue 中webpack的配置 (链式操作语法)
// vue.config.js
const path = require('path')
const webpack = require('webpack')
const CompressionWebpackPlugin = require('compression-webpack-plugin') // gzip压缩
const productionGzipExtensions = /\.(js|css|json|txt|html|ico|svg)(\?.*)?$/i // gzip匹配文件规则
const isProd = process.env.NODE_ENV === 'production'
const port = process.env.port || process.env.npm_config_port || 9526
function resolve(dir) {
return path.join(__dirname, dir)
}
const vueConfig = {
publicPath: './',
outputDir: 'client',
assetsDir: 'static',
// runtimeCompiler: true,
devServer: {
port: port,
open: false,
overlay: {
warnings: false,
errors: true
},
https: false,
hotOnly: false,
proxy: {
'/api': {
target: 'http://xxx.xxx.xxx:xxx/',
changeOrigin: true,
ws: true,
pathRewrite: {
'^/api': '/'
}
}
}
},
configureWebpack: {
name: 'vue项目中使用webpack',
plugins: [
// 忽略/moment/locale下的所有文件
// new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
],
resolve: {
alias: {
'@': resolve('src'),
}
}
},
chainWebpack(config) {
config.plugins.delete('preload') // TODO: need test
config.plugins.delete('prefetch') // TODO: need test
// set svg-sprite-loader
config.module
.rule('svg')
.exclude.add(resolve('src/icons'))
.end()
config.module
.rule('icons')
.test(/\.svg$/)
.include.add(resolve('src/icons'))
.end()
.use('svg-sprite-loader')
.loader('svg-sprite-loader')
.options({
symbolId: 'icon-[name]'
})
.end()
// set preserveWhitespace
config.module
.rule('vue')
.use('vue-loader')
.loader('vue-loader')
.tap(options => {
options.compilerOptions.preserveWhitespace = true
return options
})
.end()
config
// https://webpack.js.org/configuration/devtool/#development
.when(process.env.NODE_ENV === 'development',
config => config.devtool('cheap-source-map')
)
config
.when(process.env.NODE_ENV !== 'development',
config => {
config
.plugin('ScriptExtHtmlWebpackPlugin')
.after('html')
.use('script-ext-html-webpack-plugin', [{
// `runtime` must same as runtimeChunk name. default is `runtime`
inline: /runtime\..*\.js$/
}])
.end()
config
.optimization.splitChunks({
chunks: 'all',
cacheGroups: {
libs: {
name: 'chunk-libs',
test: /[\\/]node_modules[\\/]/,
priority: 10,
chunks: 'initial' // only package third parties that are initially dependent
},
elementUI: {
name: 'chunk-elementUI', // split elementUI into a single package
priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app
test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm
},
commons: {
name: 'chunk-commons',
test: resolve('src/components'), // can customize your rules
minChunks: 3, // minimum common number
priority: 5,
reuseExistingChunk: true
}
}
})
config.optimization.runtimeChunk('single')
}
)
},
css: {
loaderOptions: {
less: {
lessOptions: {
modifyVars: {},
javascriptEnabled: true,
},
},
},
},
}
if (isProd) {
vueConfig.configureWebpack.plugins.push(
new CompressionWebpackPlugin({
filename: '[path].gz[query]',
algorithm: 'gzip',
test: productionGzipExtensions,
threshold: 10240, // 只处理比这个值大的资源,按字节计算
minRatio: 0.8, // 只有压缩率比这个值小的资源才会被处理
deleteOriginalAssets: false, // 是否删除原始资源 默认false
})
)
}
module.exports = vueConfig
babel.config.js
const isProd = ['production', 'prod'].includes(process.env.NODE_ENV)
const plugins = []
if (isProd) {
// 删除console.log
plugins.push('transform-remove-console')
}
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
],
plugins
}
如果你使用的是vscode 则可以配置jsconfig.js 文件
参考文献 segmentfault.com/a/119000001…
// jsconfig
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
}
},
"exclude": ["node_modules", "dist"]
}