npm 相关知识
-
版本号
1.2.3- 1 大版本号: 代码重构
- 2 中版本号: 代码加功能
- 3 小版本号: bug修复
-
版本号前缀 ^ ~ * 不加前缀符号就装指定版本的依赖
-
-D和-S的区别-D: dependencies-S: devDependencies- 将包发布以后 别人装这个包只会装dependencies的依赖
npm install --only=prod npm install --only=dev npm install // 全装 -
npm install过程- 寻找包版本信息文件
package-lock.json, 依照它来进行安装 - 查package.json中的依赖, 并检查项目中其他的版本信息文件
- 如果不存在包版本信息文件 就完全按照
package.json来安装 并且生成一个版本信息文件 - 如果存在版本信息文件 则只会安装
package.json中有而版本信息中没有的哪些包
- 如果不存在包版本信息文件 就完全按照
- 如果发现了新包, 就更新版本信息文件
- 寻找包版本信息文件
-
如果发现某个包和预想的不一致 应该:
- 看版本信息中该包的来源和版本 因为在安装的过程中 它的优先级是最高的
webpack
前端模块化
不进行特殊配置 只能处理js(es5及以下)
灵活loaderplugin可插拔
webpack本身只能处理es6以下版本的jsloader顺序plugin使用
作用域
概念: 代码运行时,变量、函数、对象的可访问性
全局作用域
在文件中开始写 javascript代码 就已经在全局作用域了
在js执行中 只有一个全局作用域 全局作用域的变量和资源都会挂载在全局对象上
在浏览器中 这个全局对象是window;在node中,这个全局对象是global
// 在浏览器中
a = 1 // window.a = 1
var b = 2 // window.b = 2
let c = 3 // es6 let 块作用域 window.c = undefined
局部作用域
块作用域
模块化
作用域封装
重用性
解除耦合 (把系统 分解为一个个模块 拆分一个庞大的系统解除更个部分的耦合 当系统的某个部分发生改变 模块可以帮助我们快速的定位问题 模块(把功能的实现封装在自己的内部) 只要模块暴露的接口不变 模块内部逻辑的变化并不会影响其他模块)
一个页面有多个功能 会把多个功能对应的js通过外链 如下代码:
这三个js文件共用了一个全局作用域
在任何一个文件中 进行顶层作用域的变量或函数声明 都会暴露在全局之中 使得其他脚本可能获取 它并不想要的变量
当应用的规模和复杂度上升 这些脚本之间就很容易发生命名冲突 从而导致不可预知的问题
<script src="./moduleA.js"></script>
<script src="./moduleB.js"></script>
<script src="./moduleC.js"></script>
// moduleA.js:
var name = 'Lucy'
// moduleB.js:
var name = 'Jack'
// moduleC.js:
// moduleC里想用moduleA中的name变量 但是moduleB中把moduleA中的name变量覆盖了 所以拿到的name是错误的
console.log(name)
初步解决方案: 加上命名空间
缺点: 依然不能解决文件里的变量方法被随意访问和修改(用闭包去解决)
// moduleA.js:
var a = {
name: 'Lucy',
tell () {
console.log('我的名字是', this.name)
}
}
// moduleB.js:
var b = {
name: 'Jack',
tell () {
console.log('我的名字是', this.name)
}
}
// moduleC.js:
console.log(a.name)
a.name = 'Beta'
早起模块化的写法
// moduleA.js:
var moduleA = (function() {
var name = 'Lucy'
return {
// name,
tell () {
console.log(name)
}
}
})()
// 更进一步改写 立即函数为标准模块的实现
(function(window) {
var name = 'Lucy'
function tell() {
console.log(name)
}
window.moduleA = { tell }
})(window)
模块化的演变
从立即执行函数实现方法之后 模块化方案经历了很长一段时间演化
三个比较重要的阶段: AMD COMMONJS ES6-MODULE
AMD
Asynchronous Module Definition 异步模块定义
amd规范成型较早 现在应用并不广发
使用defined定义模块 接收三个参数:
- 当前模块的id(给模块起名字)
- 当前模块的依赖
- 可以是函数/对象
- 如果是函数 将函数返回值作为定义模块的接口导出
- 如果是对象 这个对象就是当前模块的导出值 好处:
- 显示的表达了每个模块依赖的其他模块有哪些
- 模块的定义不在绑定在全局对象上 增强了模块的安全性 不必担心在别的地方被篡改
define('getSum', ['math'], function(math) {
return function(a, b) {
console.log('sum:', math.sum(a + b))
}
})
COMMONJS
2019年被提出 最开始是为了定义服务端的模块标准 而不是用于浏览器环境
之后nodeJs采用并实现了它的部分规范 在模块系统上做了以下调整
一般来说 不会严格区分COMMONJS和nodeJs的模块标准
(两种标准之间有何区别)
在CommonJS中每个文件就是一个模块 并且拥有属于它自己的作用域和上下文
// 模块的依赖通过require函数来引入
const math = require('./math')
// 通过exports将其导出
exports.getSum = function(a, b) {
return a + b
}
amd和commonJs具有同样的特性:
强调模块的依赖必须显示引入 这样做方便在维护复杂模块时 可以不必操心各个模块间引入顺序的问题
ES6 Module 推荐的模块化写法
// import 导入
import math from './math'
// export 导出
export function sum(a, b) {
return a + b
}
webpack打包
webpack与立即执行函数之间的关系- 打包的核心逻辑
// 安装依赖
/*打包*/
npm webpack -g
npm webpack-cli -g
/*监听工程目录文件改动*/
npm webpack-dev-serve -g // 监听工程目录文件改动 当修改源文件 会动态实时的重新打包 并且自动刷新浏览器
-
webpack预置了一些配置 即使在没有配置的情况下 执行webpack命令
D:\study\webpackTest>webpack // 找到当前项目目录的src/index.js开始打包 出口是<projectDir>/dist/main -
webpack运行的时候会找
webpack.config.js就会按照该文件的配置去打包 -
webpack-dev-server// "webpack-cli": "^4.7.2" // webpack-cli版本高了 不兼容webpack-dev-server 改为"webpack-cli": "^3.3.12", D:\study\webpackTest\react-demo>webpack-dev-server internal/modules/cjs/loader.js:883 throw err; ^ Error: Cannot find module 'webpack-cli/bin/config-yargs' -
常见
loaderloader加载顺序栈先进后执行// cssloader npm install css-loader npm install style-loader /** * babel 将高版本的es代码转为低版本的 */ // src/test.js: [1, 2, 3].map(e => console.log(e)) // 使用: babel src/test.js npm i @babel/core @babel/cli -g // 转换规则; 这个包核心功能转换高版本的es代码为es5 // 早些年用babel会用一系列的插件 但这样比较琐碎 babel官方会把一些插件集成到一个包里 // 使用: babel src/test.js --presets=@babel/preset-env // 在控制台打印出来 npm i @babel/preset-env /*babel支持配置文件*/ -
使用babel, babel支持配置文件:
- 在package.json里配置
{ "babel": { "presets": ["@babel/preset-env"] } }- 在.babelrc里配置 优先级最高 (babel会自动在找有没有.babelrc)
-
webpack中使用babel
- 需要使用babel-loader 它依赖于@babel/core @babel/cli
npm i babel-loader -D npm i @babel/core @babel/cli -D // 如果已经在全局装了可以不装 // 指定规则将高版本es代码转为低版本 npm i @babel/preset-env -D // 如果是react项目 转义jsx代码 npm i @babel/preset-react -D - 在webpack.config.js中配置
{ module: { rules: [ { test: /.jsx?/, exclude: /node_modules/, // 排除在外 include: [], use: { loader: "babel-loader" // babel-loader依赖@babel/core @babel/cli } } ] } }
- 需要使用babel-loader 它依赖于@babel/core @babel/cli
-
配置
html-webpack-pluginconst htmlWebpackPlugin = require('html-webpack-plugin') const path = require('path') module.exports = { plugins: [ new htmlWebpackPlugin({ // template: '/react-demo/src/index.html' // 需要处理的文件绝对路径 template: path.resolve(__dirname, '/react-demo/src/index.html'), // 需要处理的文件绝对路径 filename: 'index.html' // 打包过后的目标文件名称 }) ] } -
webpack-dev-server详细配置
启动webpack-dev-server服务 可以监听工程目录文件改动
当修改源文件 会动态实时的重新打包(并不会真的生成打包文件 可以理解为存在缓存中 ) 并且自动刷新浏览器
可以配置不去刷新浏览器 就自动更新内容 -- 热更新const webpack = require('webpack') module.exports = { devServer: { open: true, // 自动打开浏览器 hot: true // 热更新 }, plugins: [ new webpack.HotModuleReplacementPlugin() ] } // 热更新出错提示 // index.jsx: if (module.hot) { module.hot.accept(err => { console.log('热更新出BUG:', err) }) }- webpack-dev-server有自己默认配置项 也可以指定配置
/** * open 指定是否自动打开浏览器 * config 指定文件路径 不写的话 默认是找webpack.config.js */ webpack-dev-server --open // 自动打开浏览器 webpack-dev-server --config // 读webpack.config.js里的devServer配置 // 可能会将生产环境和开发环境的配置分成两个文件来配置 需要指定文件 webpack-dev-server --config build/webpack.dev.conf.js // 读build/webpack.config.dev.js里的devServer配置
- webpack-dev-server有自己默认配置项 也可以指定配置
webpack 性能调优
- 打包结果优化
- 构建过程优化
- Tree-Shaking
打包结果优化
-
打包生产环境时 webpack会自动压缩代码
webpack --mode production -
webpack也可以自定义打包工具 比如webpack官方提供了terser-webpack-plugin
// 因为考虑到压缩这个环节非常重要 webpack有专门的属性(optimization) 来存放和压缩有关的东西 const TerserPlugin = require('terser-webpack-plugin') module.exports = { optimization: { minimizer: [ new TerserPlugin({ // cache: true, // 开启缓存 属性被移除 parallel: true, // 开启多线程; 压缩是比较耗时的 terserOptions: { compress: { unused: true, // 自动剔除无用代码 drop_debugger: true, // 去除debugger drop_console: true // 去除console } }, }) ] } } /** * 之前有个Ugligy.js在压缩es5方面做得很优秀 但在es6的代码压缩上做得不够好 * 所以后来有一个Uglify-es的项目 处理es6; 由于后来没有人维护Uglify-es * tenrser-webpack-plugin 就是uglify-es项目拉的一个分支来继续去维护 */ -
webpack打包结果分析
从构建体积大的文件 下手去做优化// 安装: npm i webpack-bundle-analyzer -D const BundleAnalyzerPlugin = require('webpack-bundle-plugin').BundleAnalyzerPlugin module.exports = { plugins: [ new BundleAnalyzerPlugin() ] }
构建过程优化
构建速度和构建体积往往是耦合在一起的 打包的时候可以
- 开启缓存
- 开启多线程
HappyPack、thread-loader
nodejs是单线程模型的, 所以运行在nodejs的webpack是单线程的 所以webpack需要处理的事得一件一件的做, 要webpack多件事情一起做 需要多核cpu发挥作用
使用插件可以让webpack支持多个线程去打包; 而简单的项目使用多线程打包 会浪费更多的cpu资源 这样不仅不能加快 还会降低打包速度
在webpack中实现多线程打包 它都是从线程的维度来做这个事情的; happypack,thread-loader也好,它都是在创建线程池;
因为webpack,node都是单线程的 所以要发挥多线程优势 必须要借助进程这个维度的数量; 但是在webpack上体现出来的只有多线程 背后利用的是多进程
happyPack: 多进程模型 插件可以开启多进程 他会把任务分解成多个子进程 并发的去执行 子进程执行完后 把结果返回给主进程
thread-loader: 它是针对loader进行优化的, 它会把loader放在一个线程池里(worker) 达到多线程构建的目的 使用是必须放在所有loader之前(loader是栈顺序)
thread-loader: 构建过程中哪些是比较耗时间:
-
解析
webpack在进行文件打包时, 会对文件进行递归处理;
像jquery、echarts库 它们非常大又没有模块化标准
webpack解析这些文件耗时且没有意义 像这种解析不动的可以不解析 配置noParse被忽略的文件 不应该包括 import require defined这样的模块化的一些语句, 否则构建出来的文件 包含不能被浏览器环境下执行的语句
module.exports = { noParse: /node_modules/(jquery.js)/ } -
查找:
- 在rules匹配文件
module.exports = { rules: [ { test: /.jsx?/, exclude: /node_modules/, // 排除node_modules include: path.resolve('src'), // 匹配指定文件(可以是正则) include的优先级比exclude和test大 所以这三者冲突时 include优先级最高 } ] }
- 在rules匹配文件
其他思路:
- 还可以通过预编译来构建优化
- 还可以在loader上下手
fast-sass-loader并行的处理sass文件 比sass-loader快很多倍 - sourceMap耗时严重 设置为false
Tree-Shaking DCE的一种实现
消除无用代码 DCE
首先 webpack自己会分析es6的modules的引入情况 去除不使用的import引入
然后借助一些工具 比如tenser-plugin 对无用的模块进行删除(这些工具只有在mode 为production时会去掉无用的代码 为development时 只会去掉import引用)