psycholali / blog Goto Github PK
View Code? Open in Web Editor NEW记录和沉淀
记录和沉淀
安卓小白,出于调研“如何迁移微信聊天记录”而去破解了一下安卓机的微信聊天记录数据库文件,这个破解方式2013年就已经有人发表了文章,有兴趣可以去网上搜一搜,这篇文章主要是记录学习历程以及遇到的问题。
##具体过程##
1.聊天记录信息全保存在手机**/data/data目录里。这个目录需要root**之后,手机上才能看到此目录(但是电脑端还是不可直接查看)
2.通过adb连接手机,然后adb shell使用su权限授权/data/data 777权限,然后就可以通过电脑上的android studio里的android device monitor查看此目录结构
3.android device monitor可以选择文件并导出到电脑上,进行进一步查看
4.微信聊天记录位置:
/data/data/com.tencent.mm/MicroMsg/一串MD5 32位小写格式的文件夹/EnMicroMsg.db里
(这串MD5 32位小写格式的文件夹名是通过mm+uin然后MD5加密而成的)
5.这个EnMicroMsg.db有密码,密码是手机imei+uin 然后进行MD5的32位小写加密,并截取前7位。
然后网上大部分说这个加密算法是不会变化的(MD5(IMEI+UIN).Substring(0, 7).toLower),因为维系为了兼容以前版本,如果数据库加密算法变动,那么老版本的用户升级到新版本,老版本的数据库解密就会失败的,也就是用户看不到以前的消息内容,那不可能的
6.一般数据库软件像navicat无法打开EnMicroMsg.db,需要专门的处理加解密的数据库软件打开,我用的大部分人推荐的sqlcipher。(这是一个开源的库,听说如果版本不同,使用正确的密码也不能打开,现在使用的是v2.1版本,可以打开
8.可以去到Browse Date顶部菜单里查看数据数据:
用户基本信息——userinfo表
联系人——rcontact表
聊天记录——message表
已发视频信息——videoinfo2表
已发图片信息——imginfo2表
已发语音信息——voiceinfo2表
然后如果手机微信选择某一条信息删除,然后db就会更新为删除后的表。
参考资料:
https://www.osslab.com.tw/how-decrypt-wechat-sqlite/
https://articles.forensicfocus.com/2014/10/01/decrypt-wechat-enmicromsgdb-database/
https://blog.csdn.net/njweiyukun/article/details/54024442
一个基于布局的近似方法(A layout-based approach)
[Public] Created: April 12, 2016
Last updated: June 6, 2016 Owner: Kunihiko Sakamoto <[[email protected]][1]>
Status: draft Tracking bug
原文地址:[https://docs.google.com/document/d/1BR94tJdZLsin5poeet0XoTW60M0SjvOJQttKT-JK8HI/view#heading=h.p4gz01f31cgq][2]
翻译:psychola(第一次翻译此类文章,有翻译错误请指出哦,如若转载请注明出处哦,谢谢(^U^)ノ~)
**“首次有效渲染”*(First Meaningful Paint)*是当页面的主要内容出现在屏幕上花的时间。这将是我们对于用户感受到的加载体验的主要度量标准(Metric)。
“主要内容”*(primary content)*的定义根据不同页面而有所不同。对于博客里的文章,它是标题+首页可见的上半部分文章内容(above-the-fold text),并且其文本必须是可见的,而不是在等待字体加载。对于搜索引擎,它是搜索结果。如果一个图片对于页面来说很重要(例如e-commerce产品页),则 “首次有效渲染”需要这个图片是可见的状态。所以,一个渲染如果只有页面头部、导航栏,或加载提示标(如旋转的图标)就不是合格的“主要内容”。
“首次有内容的渲染”*(First Contentful Paint)*是当一些有内容的东西(如文字、图像、canvas画布或SVG)第一次被渲染花的时间。它在UMA和trace event中需要用到,并且它可以作为“首次有效渲染”时间的近似值,但它经常捕获没有意义的渲染,如头部和导航栏。
在本文中,我们提出一个基于布局的方法去近似求“首次有效渲染时间”,对于用户感知到的“首次有效渲染”时间,这个方法的准确度有77%(总共198个页面)。
随着页面加载, 布局对象逐步被加到布局树*(layout tree)*中。
图1显示了当加载谷歌搜索结果页面时,被逐步加载到布局树中的布局对象的数量。图2是那个页面加载的视觉进展过程,从WebPageTest结果中可以看到。 (完整的WPT网页测试结果)。
“首次有内容的渲染”(1.577秒时)只有页面头部,并且到那个时候60个“布局对象”被添加到页面。在1.76 s时,顶部广告已渲染了一部分,并且“布局对象”总数是103个。 在大约1.86 s时, 搜索结果出现了, 并且261个“布局对象”被添加到“布局树”中。接下来的渲染(1.907秒时)是“首次有效渲染”。在那之后,剩下的搜索结果和页脚都出现在了下面那些不可见的区域中。视觉上,页面的渲染在2.425秒时完成。
图1:当加载谷歌搜索结果页面时,被逐步加载到布局树中的布局对象的数量。
图2:页面加载的视觉进展过程
从这个例子中,你可以观察到”布局对象”数量与页面加载的“完整性”密切相关。
(LayoutAnalyzer)通过几个计数器收集有关布局操作的信息,并释放他们作为(trace events)。我用这些计数器做实验并且发现 有大量的 “尚未布局的布局对象”*(LayoutObjectsThatHadNeverHadLayout)的计数器是“首次有效渲染”的一个很好的参考项。其他的计数器, 像“尺寸改变的布局块”( LayoutBlockSizeChanged)或“全都布局好了的布局对象”(TotalLayoutObjectsThatWereLaidOut)都对“首次有效渲染”参考更小。这意味着,按照渲染的重要程度, 新的布局对象的数量比“重新布局的布局对象”的数量(relayouts)*更有意义。
所以,这是我们第一次求的近似的“首次有效渲染时间”:
“首次有效渲染时间 ”= 紧跟着“最大布局变化”之后的渲染时间点
“最大布局变化”*(Biggest layout change)*就是 有着大量 “尚未布局的布局对象” 的布局。
在图1中, “最大布局变化”是在1.86秒时, 所以下一个渲染时间点(1.907秒)就是“首次有效渲染时间”。
为什么用“布局对象”的总数来定义“首次有效渲染时间”呢?例如,我们能不能定义“首次有效渲染时间”为 百分之多少的布局已经完成呢?问题是我们不能可靠地区分这些东西,当页面完成时。我们不希望根据我们判断一个页面是否加载完成的不同, 使得计算的“首次有效渲染时间”也大幅度的不同。
这一基本方法比“首次有内容的渲染”得到更好的结果 (见后文中的“评估单元”),但在许多情况下它仍然不能检测到真正有意义的渲染。
现在,存在一些这个基本方法不起作用的典型原因, 所以我们要通过添加一些算法,使我们即使在这些情况下也能显著地提高精确度。
图3是http://us.weibo.com/gb页面在视觉上的加载过程([完整的WebPageTest结果](http://www.webpagetest.org/result/160329\_NZ\_GNA/))。
图4显示了在页面加载期间被添加的布局对象的数量。“首次有效渲染时间”是6.047 s(当在一堆文本内容出现时),但最大的布局变化大约是在23.8 s。接下来的渲染(24.25秒时)在视觉上是没有意义的,因为在23.8 s发生的布局变化时添加的对象在页面底部,到了可见区域之外。
图3: 网站http://us.weibo.com/gb的视觉上的加载过程
图4: 页面加载期间被添加的布局对象的数量变化图
我们该如何阻止这些“低于折叠的布局”(below-the-fold layouts)混淆我们的度量标准呢? 对于我们来说,最理想的是去检查每个布局是否是可见的, 但是如果想让这个变成一个UMA度量标准, 就必须避免布局期间所带来的昂贵的计算。所以, 当页面比屏幕高度长的时候,我们降低了页面的布局。具体来说,我们使用“布局意义”来代替“布局对象的原始数量”,如下:
“布局意义”( layout significance )= 添加的布局对象的数量/ max(1,页面高度/屏幕高度)
例如,在图4中最大的布局变化发生的时候(23.8秒),页面高度是屏幕高度的4.25倍, 所以这个“布局意义”是布局对象的数量 除以4.25。图5显示的布局过程与图4类似,但图5使用了上面定义的“布局意义”。现在,最重要的布局变化是在5.89秒, 就在“首次有效渲染”(6.047秒)之前。
图6: http://www.msn.com/页面的视觉加载过程
当一个网页字体在加载时,文本已经布局了(备用字体Fallbackfont的字体度量标准是这样判断的),但文本其实并没有在字体阻塞期间渲染出来(默认情况下, 字体阻塞期间是从加载开始的3秒)。Blink浏览器引擎(Blink是一个由Google主導开发的开源浏览器排版引擎)布局层并不关心文本是不是可见的, 但由于文本对于用户感受到的加载体验是很重要的,所以我们的“首次有效渲染”度量标准应把页面字体的可见性考虑进去。
所以我们引入算法:如果当布局发生时还存在加载中的字体,布局的变化统计将会被推迟,直到该字体显示出来。(如果三秒内加载了就用该字体,否则超时了就用备用字体显示)。
但是,把这条规则应用到所有网站字体显得太勉强了——较小的字体,如图标字体并不会阻塞“首次有效渲染时间”。在我实验的实现中, 超过200个字节的网页字体才会阻塞“首次有效渲染时间”。
● 这个测量只是基于新的布局对象,不考虑已布局的布局对象的大小或位置的改变。所以这并不适合“稳定的布局”(layout stabilized)的度量标准。
● 对于一些页面, “首次有效渲染”的关键因素是图片。对于等待中的图片,我们可能需要一些算法, 就像等待加载的字体用的算法一样。
● 由于这个指标是基于布局对象的,所以这是不受“未连接到布局树的DOM元素”的影响 (例如display:none的元素)。然而,一些页面渲染内容在覆盖物之下, (例如,www.flipkart.com的初始页),或在一个透明层之上(例如www.adobe.com)去实现一个fadein的加载效果。在这些情况下,我们的方法发现布局变化对用户来说是看不见的, 而且计时是毫无意义的。
● 页面高度算法使这个度量标准依赖于屏幕的大小。这是很正常的,因为我们的兴趣在是折叠渲染区域之上的部分, 但推导这个度量标准可能有点复杂。
为了评估这个方法, 我使用修补版的ChromeAPK,已经在200个热门网站(基于alexa-top,包括子页面)上跑了WPT测试。然后我下载WPT 的trace.json文件,并且通过运行trace event来计算上文中提到的基于布局的“首次有效渲染时间”。WPT还生成截图幻灯片,所以我们可以比较计算的“首次有效渲染时间”和人为选择的“用户感受到的首次有效渲染时间”的区别。
结果: https://docs.google.com/spreadsheets/d/1FEcDwXfgMf5rw1lNn07Cm04Ar7AdPzmt0aAufMsnnk/ edit?usp=sharing
截屏 (橙色高亮的是检测到的“首次有效渲染时间”): https://goo.gl/wOHl2s
表1总结了结果。“首次有内容的渲染FCP”是我们目前有的最好的近似值,它匹配了52.5%的“用户感知的首次有效渲染时间”[标注1]。而“基本布局对象”(不包含“页面高度算法”或“字体算法”)的准确率有57.1%,略优于“首次有内容的渲染” 。同时包含“页面高度算法”和“网页字体算法”这两个算法的计算准确率高达77.3%。
(标注1)在以前的评估中,我们使用了宽松定义的“首次有效渲染”(屏幕上的部分内容是可读的)。现在我们使用更严格的定义————页面的主要内容必须是易读的。
表1:评估结果的总结
实验的实现包括了:
(1)Blink浏览器引擎改变(Blink side change),这个改变 增强了LayoutAnalyzer对页面高度和网页字体可见性信息的trace event;
(2)一个脚本 ,该脚本处理trace event去计算最大的布局变化。
这个实现方案很容易变成一个TBMv2度量标准*(metrics)*,而这个标准可用于像Page Cycler V2一样的基准。
过时了,请看 UMA design doc(UMA设计文档)
我们希望去收集“首次有效渲染时间”作为一个UMA度量标准。Finch和heartbeat依赖于UMA, UMA是唯一我们有的能理解真实用户体验的系统。
LayoutAnalyzer默认情况下是禁用的,因为它有一些计算开销。我们不需要所有的LayoutAnalyzer计数器来计算“首次有效渲染时间”, 只需要计算被添加到布局树的布局对象的数量,这个数量可以以不同的方式收集。
实现计划:PaintTiming持有一个渲染的时间戳,这个时间戳目前在最大的布局变化之后。当有更重要的布局变化时,时间戳会更新。这个时间戳会被发送到浏览器进程的PageLoadMetrics中,并且当用户从页面导航出去时它会作为UMA直方图。
注意, “首次有效渲染时间”的时间戳可以在页面加载期间更改几次。如果页面加载在完成之前失败, “页面加载度量标准”将报告暂时的时间,这个时间可能不是很有意义,例如导航栏或旋转图标。页面加载度量标准” 在单独的直方图里记录“失败的导航栏”的时间 (“页面加载失败时间” *)。对于失败的页面加载,我们需要非常小心地解读它的“首次有效渲染时间”度量标准。
第一步 (基于trace的实现) 来了, 并且它会作为一个TBMv2 metric(TBMv2标准)。现在,你可以从跟踪观察器来看“首次有效渲染时间”。
你可以去chrome://tracing使用此功能, 并勾选下方这两项去记录一个trace:
● blink.user_timing
● loading
“首次有效渲染时间”的值可以在“度量标准”侧板中看到
(见下图)。
另外,这些标准可以从一个跟踪文件里计算得到,通过使用
这个命令:
chromium/src$ third_party/catapult/tracing/bin/run_metric trace.json loadingMetric
第二步 (UMA): 第一个版本的UMA来了。目前, 直方图是作为实验的标记内容,在促进他们的实验之前,我们想做一些改进。(跟踪在https://crbug.com/632081).
"Is it useful?" metric (aka. First meaningful paint): layoutbased approach loadingdev thread implementation tracking bug
之前写的《webpack入门必备(一):基础配置》主要介绍了webpack基础解析所需的loader/plugin。而随着日常webpack的使用,我们会更多关注如何构建更快、构建产物更小、构建产物符合规范...希望这篇文章可以让你找到答案。
这里介绍的主要的几种优化配置如下所示:
如果你有没用过的配置可以接着看下面的具体使用方法,如果你已经很熟悉了则可以跳过此节~
配置来确保转译尽可能少的文件(exclude 的优先级高于 include)
const rootDir = process.cwd();
{
test: /\.(j|t)sx?$/,
include: [path.resolve(rootDir, 'src')],
exclude: [
/(.|_)min\.js$/
],
}
PS. 相比exclude可以多用include
如果一些库不依赖其它库的库,不需要解析他们,可以引入来加快编译速度。
noParse: /node_modules\/(moment|chart\.js)/
忽略第三方包指定目录。 (他是webpack 内置的插件)
例如: moment (2.24.0版本) 会将所有本地化内容和核心功能一起打包,我们就可以使用 IgnorePlugin 在打包时忽略本地化内容(语言包),见下图。
plugins: [
// 表示忽略moment下的locale文件夹内容
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
]
把 thread-loader 放置在其它 loader 之前,那么它之后的 loader 就会在一个单独的 worker 池中运行。
// 项目中babel-loader一般耗时比较长,所以可以配置thread-loader
rules: [
{
test: /\.jsx?$/,
use: ['thread-loader', 'cache-loader', 'babel-loader']
}
]
运行在Node.js上的webpack是单线程,将文件解析的任务拆分由多个子进程并发进行,然后子进程处理完任务后再将结果发送给主进程,提升项目构件速度。
(但是因为进程的分配和管理也需要时间,所以使用后不一定快,需要项目接入实验一下)
const Happypack = require("happypack");
module.exports = {
module: {
rules: [
{
test: /\.js[x]?$/,
use: "Happypack/loader?id=js",
include: [path.resolve(__dirname, "src")],
},
{
test: /\.css$/,
use: "Happypack/loader?id=css",
include: [
path.resolve(__dirname, "src"),
path.resolve(__dirname, "node_modules", "bootstrap", "dist"),
],
},
{
test: /\.(png|jpg|gif|jpeg|webp|svg|eot|ttf|woff|woff2|.gexf)$/,
use: "Happypack/loader?id=file",
include: [
path.resolve(__dirname, "src"),
path.resolve(__dirname, "public"),
path.resolve(__dirname, "node_modules", "bootstrap", "dist"),
],
},
],
},
plugins: [
new Happypack({
id: "js", //和rule中的id=js对应
//将之前 rule 中的 loader 在此配置
use: ["babel-loader"], //必须是数组
}),
new Happypack({
id: "css", //和rule中的id=css对应
use: ["style-loader", "css-loader", "postcss-loader"],
}),
new Happypack({
id: "file", //和rule中的id=file对应
use: [
{
loader: "url-loader",
options: {
limit: 10240, //10K
},
},
],
}),
],
};
在性能开销较大的loader处使用,将构建结果缓存中磁盘中。
(默认存在node_modueles/.cache/cache-loader目录下。 )
cacheDirectory例子:
rules: [
{
test: /\.(j|t)sx?$/,
use: [
{
loader: 'babel-loader',
options: {
cacheDirectory: true,
},
}
}
]
cache-loader例子:
rules: [
{
test: /\.(css)$/,
use: [
{ loader: 'style-loader' },
{ loader: 'cache-loader' },
{ loader: 'css-loader' },
{ loader: 'postcss-loader' }
]
}
]
将复用性较高的第三方模块打包到DLL中,再次构建时直接复用,这样只需重新打包业务代码。
(注意是DLL缓存是大大缩短了首次构建时间
,像之前的cache-loader优化都是缩短rebuild时间
)
使用相关插件:
具体步骤:
(1) 新增一个webpack配置去编译DLL文件([name].dll.js
、[name].manifest.json
)
// 新增一个webpack-dll.config.js配置文件
const path = require('path');
const DllPlugin = require('webpack/lib/DllPlugin');
const distPath = path.resolve(__dirname, 'dll');
module.exports = {
entry: {
// 把 React 相关模块的放到一个单独的动态链接库
react: ['react', 'react-dom'],
// 把项目需要所有的 polyfill 放到一个单独的动态链接库
polyfill: [
'core-js/fn/object/assign',
'core-js/fn/object/entries',
...
],
},
output: {
// 输出的动态链接库的文件名称,[name] 代表当前动态链接库的名称(react 和 polyfill)
filename: '[name].dll.js',
path: distPath,
// 存放动态链接库的全局变量名称,例如对应 react 来说就是 _dll_react
// 之所以在前面加上 _dll_ 是为了防止全局变量冲突
library: '_dll_[name]',
},
plugins: [
// 接入 DllPlugin
new DllPlugin({
// 动态链接库的全局变量名称,需要和 output.library 中保持一致
// 该字段的值也就是输出的 manifest.json 文件 中 name 字段的值(_dll_react)
name: '_dll_[name]',
context: process.cwd(),
// 描述动态链接库的 manifest.json 文件输出时的文件名称
path: path.join(__dirname, 'dll', '[name].manifest.json'),
}),
],
};
// package.json里新增dll的构建命令
"scripts": {
"dll": "webpack --config webpack-dll.config.js",
}
(2) dev构建时,告诉 Webpack 使用了哪些动态链接库
// webpack.config.js文件
const DllReferencePlugin = require('webpack/lib/DllReferencePlugin');
plugins: [
// 使用的动态链接库(react和polyfill的)
new DllReferencePlugin({
context: process.cwd(),
manifest: path.join(rootDir, 'dll', 'react.manifest.json'),
}),
new DllReferencePlugin({
context: process.cwd(),
manifest: path.join(rootDir, 'dll', 'polyfill.manifest.json'),
}),
...
]
(3) html template里引入文件
因为我这里只是本地构建加速,所以就以dev的方式引入
<script src="./dll/polyfill.dll.js?_dev"></script>
<script src="./dll/react.dll.js?_dev"></script>
到这DLL就配好了。有些人可能比较好奇react.dll.js
和react.manifast.js
到底是什么文件,做了什么事?你看看他两个文件就知道啦~
react.dll.js
其实主要就是所引用模块的代码集合react.manifast.js
则写明包含哪些模块、模块路径// react.dll.js文件部分内容如下所示。
var _dll_react = (function(modules) {
// ... 此处省略 webpackBootstrap 函数代码
}([
function(module, exports, __webpack_require__) {
// 模块 ID 为 0 的模块对应的代码
},
function(module, exports, __webpack_require__) {
// 模块 ID 为 1 的模块对应的代码
},
// ... 此处省略剩下的模块对应的代码
]));
// react.manifast.js文件部分内容如下所示。
{
// 描述该动态链接库文件暴露在全局的变量名称
"name": "_dll_react",
"content": {
"./node_modules/process/browser.js": {
"id": 0,
"meta": {}
},
// ... 此处省略部分模块
"./node_modules/react-dom/lib/ReactBrowserEventEmitter.js": {
"id": 42,
"meta": {}
},
...
}
常用工具:speed-measure-webpack-plugin
使用方法:用其来包裹 Webpack 的配置
这里介绍的主要的几种优化配置如下所示:
@babel/plugin-transform-runtime
具体使用:
将业务代码和第三方依赖库进行分包,减小index.js的大小;
抽离多页应用的公共模块,单独打包。公共代码只需要下载一次就缓存起来了,避免了重复下载。
optimization: {
minimize: false,
moduleIds: 'named',
splitChunks: {
chunks: 'all',
minSize: 30000,
maxSize: 0,
minChunks: 1,
maxAsyncRequests: 6,
maxInitialRequests: 6,
automaticNameDelimiter: '~',
name: true,
cacheGroups: {
polyfill: {
test: /[\\/]node_modules[\\/](core-js|@babel|regenerator-runtime)/,
name: 'polyfill',
priority: 70,
minChunks: 1,
reuseExistingChunk: true
},
lib: {
test: /[\\/]node_modules[\\/]/,
name: 'lib',
chunks: 'initial',
priority: 3,
minChunks: 1,
},
...
}
}
}
提取所有页面所需的helper函数到一个包里,避免重复注入
"plugins": [
"@babel/plugin-transform-runtime"
...
]
如果使用ES6的import 语法,那么在生产环境下,会自动移除没有使用到的代码。
(1) 具体配置
const TerserPlugin = require('terser-webpack-plugin');
const config = {
// 生产模式下tree-shaking才生效
mode: 'production',
optimization: {
// Webpack 将识别出它认为没有被使用的代码,并在最初的打包步骤中给它做标记。
usedExports: true,
minimizer: [
// 删除死代码的压缩器
new TerserPlugin({...})
]
}
};
(2) 哪类代码会被shake掉?以下有一些事例
// no tree-shaking
import Stuff from './stuff';
doSomething(Stuff);
// tree-shaking
import Stuff from './stuff';
doSomething();
// tree-shaking
import './stuff';
doSomething();
// no tree-shaking
import 'my-lib';
doSomething();
// 全部导入 no tree-shaking
import _ from 'lodash';
// 具名导入 tree-shaking
import { debounce } from 'lodash';
// 直接导入具体的模块 tree-shaking
import debounce from 'lodash/lib/debounce';
(3) 什么叫有副作用的代码?
只要被引入,就会对应用程序产生重要的影响。 (一个很好的例子就是全局样式表,或者设置全局配置的js文件。)
(4) 有副作用的代码我们不希望被shake,我们可以配置如下
// 所有文件都有副作用,全都不可 tree-shaking
{
"sideEffects": true
}
// 没有文件有副作用,全都可以 tree-shaking
{
"sideEffects": false
}
// 只有这些文件有副作用,所有其他文件都可以 tree-shaking,但会保留这些文件
{
"sideEffects": [
"./src/file1.js",
"./src/file2.js"
]
}
(5) 注意,babel配置需要配modules: false
,忽略import/export代码编译
const config = {
presets: [
[
'@babel/preset-env',
{
// commonjs代码不能被tree-shaking
// 所以babel保留我们现有的 es2015 import/export 语句,不进行编译
modules: false
}
]
]
};
常用工具:webpack-bundle-analyzer
使用方法:用其来包裹 Webpack 的配置
生产环境构建时,会检查构建产物里是否存在es6语法。有则抛出错误并提示你去进行babel编译,这样避免了构建产物不合要求的情况。
具体使用例子:
// package.json 命令里加上es-check检查
"dist:basic": "rimraf public && cross-env NODE_ENV=production webpack --config webpack-dist.config.js && es-check es5 ./public/**/*.js"
编译缓存就是在首次编译后把结果缓存起来,在后续编译时复用缓存,从而达到加速编译的效果。
webpack5默认开启编译缓存,缓存默认是在内存里,你可以自定义。
module.exports = {
cache: {
// 将缓存类型设置为文件系统
type: "filesystem",
// 缓存的位置(默认是node_modules/.cache/webpack)
cacheDirectory: path.resolve(__dirname, '.temp_cache'),
// 指定构建过程中的代码依赖。webpack将使用这些项目以及所有依赖项的哈希值来使文件系统缓存无效。
buildDependencies: {
// 当配置文件内容或配置文件依赖的模块文件发生变化时,当前的构建缓存即失效。
config: [__filename],
// webpack.config、loader和所有从你的配置中require的模块都会被自动添加。如果有其他的东西被构建依赖,你可以在这里添加它们
},
// 指定缓存的版本。当需要更新配置缓存时,通过设置此版本使缓存失效。
version: '1.0'
}
}
一些参数注解
cache: true
就是 cache: { type: 'memory' }
的别名type
: 'filesystem'|'memory'。version
: 当配置文件和代码都没有发生变化,但是构建的外部依赖(如环境变量)发生变化时,预期的构建产物代码也可能不同。这时就可以使用 version 配置来防止在外部依赖不同的情况下混用了相同的缓存。例如,可以传入 cache: {version: process.env.NODE_ENV},达到当不同环境切换时彼此不共用缓存的效果。长效缓存指的是能充分利用浏览器缓存,尽量减少由于模块变更导致的构建文件hash值的改变,从而导致文件缓存失效。
(由于moduleId和chunkId确定了,构建的文件的hash值也会确定。)
之前需要通过如下配置达到长效缓存:
plugins: [
- new webpack.NamedModulesPlugin(),
+ new webpack.HashedModuleIdsPlugin(),
或者配置
optimization.moduleIds = 'hashed’
optimization.chunkIds = 'named'
配置说明:
Webpack5采用新的算法,生产模式下默认启用如下配置不仅实现长效缓存,还减少了文件打包大小:
optimization.chunkIds: "deterministic"
optimization.moduleIds: "deterministic"
mangleExports: “deterministic"
PS.具体采用的算法还需要进一步深入研究~
Webpack 4版本附带了大多数Node.js核心模块的polyfill,一旦前端使用了任何核心模块,这些模块就会自动应用,导致polyfill文件很大,但是其实有些polyfill是不必要的。
而现在webpack5将不会自动为Node.js模块添加Polyfills,需要开发者手动添加合适的Polyfills。
升级迁移至webpack5需要注意:
1.嵌套tree-shaking
能够跟踪对export的嵌套属性的访问,分析模块的export和import的依赖关系,去掉未被使用的模块
// inner.js
export const a = 1;
export const b = 2;
// module.js
export * as inner from './inner';
// or import * as inner from './inner'; export { inner };
// user.js
import * as module from './module';
console.log(module.inner.a); // 在此示例中,可以在生产模式下移除导出 b。
2.内部模块tree-shaking(深度作用域分析)
新属性optimization.innerGraph分析模块导出和导入之间的依赖关系,在生产模式下默认启用。
import { something } from './something';
function usingSomething() {
return something;
}
export function test() {
return usingSomething();
}
// 在使用 test 导出时才使用 something。
可以分析以下符号:
3.package.json 中的“sideEffects”标志允许将模块手动标记为无副作用,从而在不使用它们时将其移除。
webpack 5 还可以根据对源代码的静态分析,自动将模块标记为无副作用。
更多Webpack5的内容推荐阅读:
1、分享监听接口
分享消息给好友时,开发者将无法从callback获知用户是否分享完成,也无法在分享后立即获得群ID。请参考调整指引
2、getUserInfo接口
用户需要点击组件后,才可以触发登录授权弹窗、授权自己的昵称头像等数据。请参考调整指引
3、openSetting接口
用户需要点击行为后,才可以跳转打开设置页,管理授权信息。请参考调整指引
官方文档上提到了几种检测方法,但是测试后发现有些没有用:
1)**wx.canIUse()**对一些api没有用
2)if (wx.apiXX) { wx.apiXX(); }也不完全有用
所以,最后通过直接比较当前版本号和目标版本号来实现兼容。
/**
* 比较两个版本号的大小,用于兼容小程序不同版本的api时比较版本号(v1 > v2则返回1)
* @param {*} v1
* @param {*} v2
*/
export function compareVersion(v1, v2) {
v1 = v1.split('.')
v2 = v2.split('.')
var len = Math.max(v1.length, v2.length)
while (v1.length < len) {
v1.push('0')
}
while (v2.length < len) {
v2.push('0')
}
for (var i = 0; i < len; i++) {
var num1 = parseInt(v1[i])
var num2 = parseInt(v2[i])
if (num1 > num2) {
return 1
} else if (num1 < num2) {
return -1
}
}
return 0
}
const SDKVersion = wx.getSystemInfoSync().SDKVersion; // 获取版本
if(T.compareVersion(SDKVersion, '2.0.7')>=0) { ... }
小程序跳转只能实现 同一公众号下关联 的小程序跳转
一个公众号可关联10个同主体的小程序,3个不同主体的小程序。
一个小程序可关联3个公众号。
(p.s. 所以想跳转像苏宁小程序,只能把我们小程序关联到他们小程序的公众号下)
(1)使用navigator组件(仅用于微信2.0.7以上版本)
<navigator target="miniProgram" app-id="{{changeNewAppid}}" path="{{changeNewUrl}}" version="release" @tap="goMiniprogram">
跳转mp
</navigator>
(2)使用navigateToMiniProgram来兼容微信2.0.7以下(但即将废弃) >=1.3.0
goMiniprogram() {
const SDKVersion = wx.getSystemInfoSync().SDKVersion; // 获取版本
if(T.compareVersion(SDKVersion, '2.0.7')>=0) {
wx.navigateToMiniProgram({
appId: this.changeNewAppid,
path: this.changeNewUrl,
envVersion: 'release',
success(res) {
console.log('打开mp成功')
}
})
}
}
调试注意:
开发者工具上不会显示跳转,但我们可以从回调函数里log打印信息,只有真机调试才可以跳转。
格式:pages/list?source=wxmpcz01 (不要appid,也不要pages前加/)
思路:
1.小程序不能分享到朋友圈,只能通过保存图片的形式分享。
2.如果保存静态图片,可以直接调接口就行,但是需求是动态的图片(获取用户的头像),所以需要用canvas画图,然后保存成2倍/3倍图。
优化的问题:
一、文字有用特殊字体,但是字体文件都比较大。所以需要用fontmin抽取出所需字的字体文件,最后上线页面从原来的3.7MB变成29KB字体woff/eot/ttf文件。
1.canvas的clip()裁剪方法,只对第一次裁剪的图有效,后面裁剪都无效。
解决方案:制作一张和头像图片一样大的中间有个n个圆形镂空(中间透明)的图片绘制在头像上,在视觉上给头像做出圆形的效果。
2.不能给文字设置字体,所以需求需要特殊字体时用的图片。
3.保存canvas为图片时,ctx.draw()需要加个定时器,晚些执行canvasToTempFilePath().否则保存下来的是空白图片。
ctx.draw(true, setTimeout(function() { // 延迟一下
wx.canvasToTempFilePath({
x: 0,
y: 0,
width: 2079,
height: 2181,
destWidth: 2079,
destHeight: 2181,
canvasId: 'myCanvas',
success: function(res) {
self.data.savedImgUrl = res.tempFilePath;
self.saveImageToPhoto();
}
});
}, 400));
// 保存图片到相册
saveImageToPhoto() {
const that = this;
if (this.data.savedImgUrl !== '') {
wx.saveImageToPhotosAlbum({
filePath: this.data.savedImgUrl,
success: function() {
that.imgSaveLoading = false;
that.$apply();
wx.showModal({
title: '保存图片成功',
content: 'xxxx',
showCancel: false
});
},
fail: function(res) {
console.log(res);
that.imgSaveLoading = false;
that.$apply();
if (res.errMsg === 'saveImageToPhotosAlbum:fail cancel') {
wx.showModal({
title: '保存图片失败',
content: '您已取消保存图片到相册!',
showCancel: false
});
} else {
wx.showModal({
title: '提示',
content: '保存图片失败,您可以点击确定设置获取相册权限后再尝试保存!',
complete: function(res) {
if (res.confirm) {
wx.openSetting({}); // 打开小程序设置页面,可以设置权限
} else {
wx.showModal({
title: '保存图片失败',
content: '您已取消保存图片到相册!',
showCancel: false
});
}
}
});
}
}
});
}
}
参考文章:
小程序05 canvas绘图并保存到相册
canvas坑
如果使用右上角的默认分享功能,可以调用onShareAppMessage(), 如果自定义按钮分享则是使用<button open-type="share"> 结合 onShareAppMessage里加一些判断。(注意:成功/失败callback于2018/10/10废弃)
<button id="share-family" open-type="share">分享给家人</button>
onShareAppMessage(res) {
let shareType = 'friend';
let title = '送你一个公益礼包,快去打开看看是什么!';
let path = 'XXXX'; // 右上角分享时
if (res.from === 'button') {// 来自页面内转发按钮,根据不同的#id分享不同的链接等操作
shareType = res.target.id.split('-')[1];
if (shareType === 'family') { // 分享结果页
path = ‘XXXXXX2’;
title = '我有个公益礼包需要和家人一起打开,你快点进来!' ;
}
....
}
return { // 分享的title,path里都可有变量
title: title,
path: path,
imageUrl: 'xxx',
success(res) {
console.log(res);
做一些成功后的操作...
},
fail(res) {
console.log(res);
}
};
}
1.在微信公众平台-小程序的模板中心先申请一个消息模板
2.服务消息触达只有支付的和提交表单才能触发服务触达通知。而支付这个方法不符合场景,所以使用提交表单:把任意一个文本改造成一个空表单的按钮,然后点击上报formid给后台。(1次提交表单可下发1条,多次提交下发条数独立,相互不影响。)
3.后台使用formid后调用相应触达的接口。
/**
前提条件:
1.已获取access_tocken。
(参考接口:https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appId}&secret=${appSecret})
2.已获取openId。
(参考接口:https://api.weixin.qq.com/sns/jscode2session?appid=${G.appId}&secret=${ appSecret}&js_code=${code}&grant_type=authorization_code)
**/
// 关键代码:
<form bindsubmit="formSubmit" report-submit>
<button form-type="submit">
一个普通文本,把他改造成一个按钮,点击提交空表单来上报formid
</button>
</form>
formSubmit(e) {
const { formId } = e.detail; // 获得formid
...
},
注意:
1.只能手机调试,我用开发工具打印出来的formId: "the formId is a mock one"并不是数字串。
2.每个formid只能给当前用户推送的时候用 不能给其他人用。
除官方api外的参考文章:
微信小程序实例:创建下发模板消息实例
手把手教你开发微信小程序之模版消息
开发 | 教你突破小程序模板消息的推送限制
wx.getUserInfo接口是获取用户信息(昵称,头像等)的接口,在官方文档上写是即将废弃。现在开发版和体验版已经废弃(调用接口默认直接fail),但是现网版本还是可以使用(会出现系统弹弹窗),官网更新说于2018/10/10废弃。
目前,有两种兼容方式:
1.如果只是单纯展示用户头像或昵称,可以使用 <open-data > 组件。但是这个有局限性,只能显示,却获取不到信息,比如:后台接口需要前端传递用户昵称或头像信息时。
<open-data type="userAvatarUrl"></open-data>
2.使用**<button open-type="getUserInfo">**,引导用户主动进行授权操作(适配v1.3.0以上版本)
<button open-type="getUserInfo" bindgetuserinfo="onGotUserInfo"\>
onGotUserInfo(e){
const { errMsg, userInfo } = e.detail;
if(errMsg.indexOf('ok') != -1) { //同意授权
info = e.detail.userInfo;
}
const { avatarUrl, nickName } = info; // 获取到了头像,昵称等
...
},
参考以下api的三种api可以获取二维码,但是有局限性。比如说:可接受path参数较长,生成个数受限;有的可接受页面参数较短,生成个数不受限;
https://developers.weixin.qq.com/miniprogram/dev/api/qrcode.html
1)获取access_token的接口 GET
https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=wx8dc21e89439d0e82&secret=78a130feb9f9a44e05fed6839aa4f881
2)获取二维码图片的文件流接口 POST
https://api.weixin.qq.com/wxa/getwxacode?access_token=ACCESS_TOKEN
{ "path": "pages/list" }
因为说明弹窗的文字是运营在工具上配置的一些字段,然后现在有一些关键字高亮的功能,那样运营可能要配置html字符串片段或者其他字符串片段,但小程序不可以像H5直接插入html片段,高亮实现起来比较麻烦。
网上解决方法:有的使用wxParse工具库,有的是自己写正则然后去自己拼凑标签。
目前解决方法:使用小程序的<rich-text>组件,把内容字段定义成json形式(而不是html格式,免去了手动正则匹配html标签的步骤),然后来动态生成相应标签并动态添加高亮class,但有个不足的地方就是每个text片段如果高亮class是hightlight,而不符合的会有一个空的class。
ps.不采用wxParse的原因:
需要引入7个文件然后总大小134KB,感觉比较占体积。
当用户选择一些个性化偏好设置之后,系统会把选择保存在授权缓存里,后续碰到相同授权不会再系统弹窗询问,而是默认以第一次用户的选择为准(获取地理位置也属于这种情况)。现在需求是希望能点击“获取当前地址”之后再次弹窗询问用户授权。
api bug:
如果用户拒绝第一次系统授权弹窗,后面wx.authorize()拉取授权窗口的接口将无效。(在wx.authorize的success回调里调用后台获取地址接口会直接fail, 报{errMsg: "getLocation:fail auth deny"}的错)
解决方案:
通过一个点击操作来调起openSetting。先去查用户是否授权地理位置(wx.getSetting + authSetting['scope.userLocation']), 如果未授权则打开一个自定义的弹窗询问用户是否去“设置”中打开权限(wx.showModal + wx.openSetting),然后跳转去到“设置”。如果“设置”中用户打开地理权限按钮,就在成功的callback里去调获取地址的API,后续操作就和第一次进入页面一致了。(这里的“设置”是指小程序的授权设置页,非手机设置或者自定义的设置页)
版本兼容:
2.3.0版本开始,用户只有发生点击行为后,才可以跳转打开设置页,所以不能直接调用,如onLoad里就调用。(2018/10/10生效)
1)<button open-type="openSetting" bindopensetting="openSettingFn"> 按钮兼容 (v2.0.7以上生效)
2)<button @tap="openSettingFn">自动获取地址</button>绑定的点击事件openSettingFn还是可以调用api
// 写法1
<button open-type="openSetting" bindopensetting="openSetting">自动获取地址</button>
openSetting(e) {
//判断是否获得了用户地理位置授权(v2.0.7以上版本)
const that = this;
if(e.detail.authSetting['scope.userLocation']) {
//同意用户的地理位置授权 立刻打开“设置”
const getUserLocationWrapper = function(){
API.getUserLocation().then((location)=>{
...
}).catch((error) => {
console.log('发起api 失败',error)
});
}
// 设置一个延时 因为用户打开授权按钮不能立即生效 所以会出现请求接口auth deny的问题
setTimeout(getUserLocationWrapper, 500);
}
}
// 写法2
<view class="edit-item-authorize" @tap="dealLocationAuthorize">
<text>自动获取地址</text>
</view>
dealLocationAuthorize() {
//判断是否获得了用户地理位置授权(v2.0.7以下版本)
wx.getSetting({
success: (res) => {
if (!res.authSetting['scope.userLocation']){
this.openConfirm()
}
}
})
}
// 未授权地址时,需要打开自定义的弹窗,询问用户是否去设置中打开权限
openConfirm () {
const that = this;
//此处可以打开一个modal询问,然后在success的回调里调用openSetting都行的
//wx.showModal({
//content: 'XXXX要获取您的地理位置,是否允许?',
//confirmText: "允许",
//cancelText: "不允许",
//success: function (res) {
//if (res.confirm) { //点击“确认”时打开设置页面
wx.openSetting({ // openSetting打开“设置”(v2.0.7以下版本)
success: (res) => {
const getUserLocationWrapper = function(){
API.getUserLocation().then((location)=>{
...
console.log('调用后台接口拿到位置信息', location)
}).catch((error) => {
console.log('发起调用后台接口失败',error)
});
}
// 设置一个延时 因为用户打开授权按钮不能立即生效 所以会出现请求接口auth deny的问题
setTimeout(getUserLocationWrapper, 500);
}
})
//} else {
// console.log('用户点击取消')
//}
//that.$apply();
//}
//});
}
解决过程:后来发现是请求方式的问题,dev环境都是http请求,idc则需要https的请求,所以之前只有开了调试模式才能拿到数据。然后,还发现因为调试者工具上开发时默认勾选“不校验合法域名、https证书...”的选项所以开发时未报错,如果取消勾选则会报http那个域名不在白名单内报错(我们小程序白名单用的是https的url)。
还发现了一些奇怪的现象:
像小程序开发板/体验版开了调试模式,再去打开线上小程序,本没有调试工具的线上小程序也有了调试工具。以及这三个版本小程序的缓存感觉有一定联系,可能共用。具体待实验后跟进。
本文主要是介绍git
不常用初期不太会用的命令,希望你看了能理解这些命令的使用,并在平时使用过程中一点点地刻意进行练习,逐步熟练并知道何时需要用到这些命令去解决你的问题。
( 我也在不断熟练中:D
如果你还是刚刚接触git命令,还不清楚 仓库
、工作流
、分支
、提交
的童鞋可以先看下 git使用简易指南,这个应该是我初学git看的第一份且收藏至今的指南了~ 图解很清晰易懂,真10分钟入门的资料:D
然后你会发现如下基础命令将会成为你之后几乎每天都要用到的80%的命令:
git clone [email protected]:nohosts/nohost.git
克隆远程仓库的内容到本地git pull origin master
获取远程分支master并merge到当前分支git branch -a
查看全部分支(远程+本地)git checkout -b bugFix
新建bugFix,并切换到到此分支。(如果分支已存在则去掉-b即可)git status
查看当前~~~~版本状态(是否修改)git add .
增加当前子目录~~~~下所有文件更改至暂存区git commit -m 'xxx'
提交暂存区的修改至本地的版本库, 修改备注为xxxgit push
将本地版本推送到远程分支git tag v1.0 dfb02e6e4f2f7b573337763e5c0013802e392818
增加v1.0的tag到某个提交上git merge testBranch
合并testBranch分支至当前分支`git stash
暂存本地的当前修改,将本地代码重置为HEAD状态。(如果需要取出修改,命令后加一个pop即可)git log
显示提交日志(如果想每个提交信息显示在一行,可以加上--pretty=oneline)git show dfb02e6e4f2f7b573337763e5c0013802e392818
显示某个提交的详细内容git reset --hard HEAD
将当前版本重置为HEAD注意这两个命令的区别:
git pull = fetch + merge
git pull --rebase = fetch + rebase
在 Git 中整合来自不同分支的修改主要有两种方法:
merge
以及rebase
。 在本节中我们将学习什么是“变基”,怎样使用“变基”,并将展示该操作的惊艳之处,以及指出在何种情况下你应避免使用它。——git-scm变基
说明:后面的举例每个 分支
都有不同的颜色,*前缀
表示现在所处的分支,而 commitid
都由C0、C1、C2代替每一个提交的哈希值,箭头
表示分支的继承
我们之前整合分支用的最多的就是merge了,那merge和rebase有什么区别呢?
**
merge
合并两个分支时会产生一个特殊的提交记录,它有两个父节点。简单说就是:“我要把这两个父节点本身及它们所有的祖先都包含进来。”**
git checkout master; git merge bugFix
下图中左、右两张图分别是执行如下代码前后的样子:
可以看出来,红色圈圈是最主要的改变—— merge
合并分支后,会在master分支上 新增一个C4提交
,而C4提交里面有master和bugFix代码库所有的修改。
此时的bugFix代码还没和master 同步
(颜色不同),我们还需要执行如下代码:
git checkout bugFix; git merge master
rebase
实际上就是取出一系列的提交记录,“复制”它们,然后在另外一个地方逐个的放下去。它的优势就是可以创造更线性的提交历史。
git checkout bugFix; git rebase master
下图中左、右两张图分别是执行代码前后的样子:
bugFix 分支里的内容通过 rebase
直接 复制
到 master 分支上。现在 bugFix 分支上的工作在 master 的最顶端,同时我们也得到了一个更 线性
的提交序列。
注意:提交记录 C3 依然存在(树上那个半透明的节点),而 C3' 是我们 rebase
到 master 分支上的 C3 的 副本
(内容是一样的,只是commitid更新了)。(如果master和bugFix之间没有其他commit,rebase后commitid不会更新。如果master已经有了自己新的commit,此时rebase后commitid就会更新。)
此时master还没有和bugFix 同步
(颜色不同),我们还需要执行如下代码:
git checkout master; git rebase bugFix
由于bugFix继承自master,所以 Git 只是简单的把master分支的引用向前移动了一下而已。
git rebase targetBranch originBranch
表示切换到originBranch,然后执行git rebase targetBranch
git rebase -i commitid
如上图标注的,传的commitid为你想修改的提交的 前一个commitid
。执行命令后进入vi模式,会提示你一些操作命令(p、r、e...)
你只需要在最上方修改默认的pick为你想要的操作,然后退出并wq保存即可生效。
具体操作:
pick 使用(啥也没变)
reword 使用并修改commit msg, 改后commit id也会更新
edit 使用并编辑commit时的文件
编辑后git add . 然后git commit —amend还可以更新最新的commit msg。 git rebase —continue 把后面的内容加进来并解决冲突, 最后提交。最新的commit id也更新
squash 合并commit
选择最新的commit去合并,然后continue发现这一次和上一次的commit msg都有,你可以删除只留下想要的也可以进行修改 然后 continue和push。如果你不删的话会发现全部文本行都组成了一个多行的commit msg
如果commit再往前已经没有了 就不能再squash,否则会报错( error: cannot 'squash' without a previous commit
)。然后 git rebase --edit-todo
可以继续vi编辑
fixup 合并commit到前面而且commit,commit msg也没了
drop 删除某个commit
便于理解,补充一个例子,主要是 rebase -i
做了 r,e,s,d操作
后产生的结果
注意:
如果想要恢复这一次rebase操作,则可以执行 git rebase —abort
。
如果想完全恢复本地分支到远程的状态,可以执行 git reset --hard origin/bugFix
,或者你可以 git reflog
找到对应提交记录回滚,但是有点麻烦
当你要改写的commit history还没有被提交到远仓库的时候,也就是说,还没有与他人共享之前,commit history是你私人所有的,那么想怎么改写都可以。
而一旦被提交到远程后,这时如果再改写history,那么势必和他人的history长的就不一样了。git push
的时候,git会比较commit history,如果不一致,commit动作会被拒绝,唯一的办法就是带上 -f
参数,强制要求commit,这时git会以committer的history覆写远程分支,从而完成代码的提交。虽然代码提交上去了,但是这样可能会造成别人工作成果的丢失,所以使用 -f
参数要慎重。
所以,在不用 -f
的前提下,想维持树的整洁,方法就是:在 git push
之前,先 git fetch
,再 git rebase
。
fetch
、再 rebase
、最后 push
。
cherry-pick
可以将提交树上任何地方的提交记录取过来追加到HEAD
上(只要不是 HEAD 上游的提交就没问题)。`
git checkout master; git cherry-pick C2
下图中左、右两张图分别是执行代码前后的样子:
是不是有点眼熟:D 没错 这个和rebase的效果蛮像的,这两个命令都可以实现复制提交~
git revert HEAD
是用一次新的commit来回滚之前的commit,git reset
是直接向上移动分支,删除一些commit看上去像从未提交一样。这两者看似达到的效果是一样的,其实完全不同。
git reset HEAD~1
git revert HEAD
如下所见,图1是初始状态(需要撤回 C2
提交),图2和3 是从图1分别执行 reset
和 revert
后的结果:
reset
C1
;现在我们的本地代码库根本就不知道有 C2
这个提交了revert
C2
后面多了一个新提交C2'
,而C2'
引入了更改—— 这些更改是用来撤销C2
这个提交的。也就是说C2'
的状态与C1
是相同的。注意:
# 事例
reset后的123 merge了12345 还是12345
revert后的12345(-3) merge了12345 是12345(-3)
HEAD
是一个对当前检出记录的符号引用 —— 也就是指向你正在其基础上进行工作的提交记录。HEAD
总是指向当前分支上最近一次提交记录
(如果想看 HEAD 指向,可以通过cat .git/HEAD
查看, 如果 HEAD 指向的是一个引用,还可以用git symbolic-ref HEAD
查看它的指向。)
1. 基础使用:
^
表示向上移动 1 个提交记录。~<num>
向上移动多个提交记录注意:操作符还支持链式操,如HEAD^2~3^
2. 延伸用法:
移动分支
可以直接使用 -f 选项让分支指向另一个提交。例如下面的命令会将 master 分支强制指向 HEAD 的第 3 级父提交。
git branch -f master HEAD~3
这次主要就总结了这几种“不常用”git命令,希望大家和我都可以多多练习,让他变成你需要时就可以自如使用的“常用”命令!:D
墙裂推荐一个可视化的git练习网站,很易懂好用~
推荐git系列文章
npm init
初始化文件NODE_ENV=development
可以设置环境变量,默认是开发模式。if(process.env.NODE_ENV ===xxx)
{
🌺mode: development/production
🌺entry: 【string | stringArray | object 可以多入口】源文件
(如果只是string,默认打包后产物叫main.js, object的话产物为键名)
🌺output: { // 输出
path,
filename: '[name].[chunkhash].js’, // 打包同步代码
chunkFilename: 同上 // 打包异步代码( https://www.cnblogs.com/skychx/archive/2020/05/18/webpack-filename-chunkFilename.html)
publicPath: '//7.url.cn/edu/act/' // 构建出的资源需要异步加载时,加载的cdn地址前缀(小心别写错了导致资源404)
crossOriginLoading: 用于配置JSONP这个异步插入的标签的 crossorigin 值(如anonymous)
library: 导出库的名字
libraryTarget: 这个一般是封装第三方库时。🌰例如可以配置'commonjs2', 'umd’...
}
🌺module: { //【对象】如何处理模块,通过一堆loader
noParse:不解析的部分。 // 如/moment(?!-)|node_modules\/chart\.js/, (忽略对部分没采用模块化的文件的递归解析和处理,这样做的好处是能提高构建性能)
rules: [ //【对象数组】
{
// ****条件匹配****
test: 正则匹配文件名或正则数组。🌰例如 [ /\.jsx?$/,/\.tsx?$/],
include: 包含哪些文件(路径)或路径数组
🌰例如 [
path.resolve(__dirname, 'src'),
path.resolve(__dirname, 'tests'
],
exclude: 排除哪些文件
🌰例如 path.resolve(__dirname, 'node_modules') ,因为node_modules 目录下的文件都是采用的 ES5 语法,没必要再通过 Babel 去转换。)
// **** 应用规则(里面的每一项就是一个loader)****
use: [ //【字符串数组(则由后往前执行) | 对象数组】
{
loader: ‘babel-loader’,
options:{
cacheDirectory:true,
},
enforce: 调节这个loader的执行顺序。
🌰例如'post'把该 Loader 的执行顺序放最后
}
]
}
]
}
🌺resolve: { // 翻译为解析,如何寻找模块
modules: //【数组】解析第三方模块时应该搜索的目录列表
🌰例如 [
path.resolve(rootDir, 'src'),
path.resolve(rootDir, 'src', 'node_modules'),
path.resolve(rootDir, 'node_modules'),
path.resolve(rootDir, 'src/edu_modules/ke-common/dist/es6'),
],
alias: 【对象】别名
extensions: 【数组】导入时自动帮你补齐列表里的后缀名去找那个文件。
🌰例如 ['.jsx', '.js', '.ts', '.tsx']
mainFields: 【字符串数组】引用第三方模块是引用他的哪个版本/优先级。
🌰例如 ['jsnext:main', 'browser', 'main'] 表示ES6, 浏览器,es5
}
🌺plugins: // 插件数组
🌰例如 new webpack.DefubePlugin({ 创建全局变量
a: xx
})
🌺devServer: { // webpack-dev-server配置
hot: true 模块热替换。
(DevServer 默认的行为是在发现源代码被更新后会通过自动刷新整个页面来做到实时预览,开启模块热替换功能后将在不刷新整个页面的情况下通过用新模块替换老模块来做到实时预览。)
host:ip
port: 端口
historyApiFallback: spa中如果刷新页面404,配置这个就可以指定404时跳去什么页面
}
}
{
🌺target 构建出正对不同运行环境的代码,默认是web
🌺devtool: source-map 开发调试的配置选项(🌰例如, 开发模式用cheap-module-eval-source-map, 既可以生成sourcemap耗时又最短,生产模式用hidden-source-map)
🌺watch: true 监听文件更新,文件改变后重新编译。平常默认是关闭的,但是使用devServer时,默认开启。
🌺externals: 不用重复再去打包的依赖.
🌰例如,一个npm包引用的react包不应该打到包里),或者cdn引入的资源不需要打包
{
jquery: 'jQuery',
react: 'React',
'react-dom': 'ReactDOM',
// 'react-dom/server': 'ReactDOMServer',
},
🌺optimization: //【对象】优化(webpack4之后不用自己配置,)
}
参考文章:
modules:默认使用 "commonjs"。即将代码中的ES6的import转为require。
@babel/preset-es2015
:按照ES6标准编译@babel-stage-x
: 处理尚处在提案语法的插件集合,babel@7已经不推荐使用, stage-0的功能范围最大(通常使用建议配到@babel/preset-stage-2)@babel-polyfill
: api只能被polyfill@babel-runtime + 插件@babel-plugin-transform-runtime配套使用
。提取所有页面所需的helper函数到一个包里,避免重复注入(如打包后会变成require('babel-runtime/helpers/createClass'))🌰事例:
{
"sourceMaps": true,
"presets": ["es2015","stage-2","react"], // 源码使用了哪些语法特性,需要提供支持.其实是一组plugins的集合
"plugins": [ // 插件,控制如何转换代码
[
"transform-runtime", // transform-runtime 默认会自动的为你使用的 ES6 API 注入 polyfill, polyfill 的注入应该交给模块使用者,因为使用者可能在其它地方已经注入了其它的 Promise polyfill 库。所以关闭该功能
{ "polyfill": false }
]
],
}
简单说,大部分babel 6的依赖命名从-改成了/
@babel/core 必需 根据我们的配置文件转换代码
@babel/present-env 相当于一个处理es6+规范语法的插件集合。 根据运行环境为代码做相应的编译, 包括了所有ECMA标准(bebal-preset-esxxxx),不包含state-x一些列插件,只支持最新推出版本的JavaScript语法(stage-4),如果需要支持其他stage需要额外引入。stage0表示最没定稿的新特性
@babel/runtime + @babel/plugin-transform-runtime 配套使用。 沙箱,提取所有页面所需的helper函数到一个包里,避免重复注入
🌰事例:
{
// "plugins": ["@babel/plugin-transform-arrow-functions", "@babel/plugin-transform-destructuring"] // 一个个插件不如使用preset-env就可以一次性全部引入
// "presets": ["@babel/preset-env"]
"presets": [
"@babel/preset-env",
{
"modules": false,
"useBuiltIns": "entry", // 按需引入:在入口处把所有ie8以上浏览器不支持api的polyfill引入进来
// 'usage',其功能更为强大,它会扫描你的代码,只有你的代码用到了哪个新的api, 它才会引入相应的polyfill。【试验状态,谨慎使用】
"targets": "ie >= 8" // 只有ie8以上版本浏览器不支持的语法才会被转换
}
'@babel/preset-react',
'@babel/preset-typescript',
],
"plugins": [
"@babel/plugin-syntax-dynamic-import", //动态导入
["@babel/plugin-transform-runtime", {
"corejs": 2
}] // 所有的helper函数抽离到一个包中,由所有的文件共同引用则可以减少可观的代码量。也可以为你的代码创建一个sandboxed environment(沙箱环境),这在你编写一些类库等公共代码的时候尤其重要。
]
}
🌰tsconfig事例:
{
"compilerOptions": {
"experimentalDecorators": true,
"baseUrl": ".",
"paths": {
"assets/*": ["src/assets/*"],
"components/*": ["src/components/*"],
},
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"noImplicitAny": true,
"lib": ["es6", "es2017", "dom", "dom.iterable", "scripthost"],
"target": "es5", // 编译出的代码使用ES几
"module": "ESNext", // 编译出的代码采用的模块规范
"jsx": "react",
"noUnusedLocals": true,
"typeRoots": ["node_modules/@types", "src/assets/types"],
"sourceMap": true,
"importHelpers": true, // 避免helper函数重复引入
"types": ["jest", "node"]
},
"include": ["src/**/*"],
"exclude": ["src/edu_modules/**/*", ".template/**/*"]
}
实现:
🌰方法1事例:
{
"presets": ["es2015", "stage-2", "react"],
"plugins": [
"react-hot-loader/babel",
"transform-function-bind",
"transform-class-properties",
"transform-export-extensions",
],
"env": {
"backend": {
"plugins": [
[ "webpack-loaders",
{ "config": "./webpack.config.babel.js"
, "verbose": true
}
]
]
}
}
}
}
module.exports = {
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
user: {
loader: 'babel-loader?cacheDirectory=true', // cacheDirectory用于缓存babel的编译结果,加快重新编译的速度
options: {
presets: ['@babel/preset-env'],
plugins: [
'@babel/plughin-transform-runtime',
'@babel/plugin-transform-modules-commonjs'
]
}
}
}
]
}
}
loader的配置是从右往左的,且配置时可以省略“-loader”
(如 style-loader css-loader postcss-loader/sass-loader)
sass-loader
// 解析sass为cssless-loader
// 解析less为csscss-loader
// 解析css。如@import,url()的导入语句 + 压缩cssstyle-loader
, // 将解析后的样式嵌入js代码中。(将require引入的样式嵌入js文件中,有好处也有坏处。好处是减 少了请求数,坏处也很明显,就是当你的样式文件很大时,造成编译的js文件也很大。如果css文件较大时,可以可以用extract-text-webpack-plugin分开css文件和js,这样css和js并行加载加快加载速度)postcss-loader
+ autoprefixer插件,可以为我们的样式添加前缀-webkit-,-ms- 提高兼容性url-loader
将图片转换为base64后注入到js和css中。(注意:会影响打包后文件大小,从而影响加载速度)🌰事例:
{
test: /\.png$/,
use: [{
loader: 'url-loader',
options: {
// 30KB 以下的文件采用 url-loader
limit: 1024 * 30,
// 否则采用 file-loader,默认值就是 file-loader
fallback: 'file-loader',
}
}]
}
🌰事例:
{
test: /\.(gif|png|jpe?g|eot|woff|ttf|pdf|svg|xlsx)$/,
use: [
{
loader: 'file-loader',
options: {
name: '[path][name].[ext]',
},
},
],
},
svg
可以用图片用的file-loader和url-loader, 也可以用raw-loader ,svg-inline-loader。后面两种是应用于js引入时,输出是<svg xxx>参考文章:
zhengweikeng/blog#9
webpack-merge
:提取公共配置,分离生产环境和开发环境的配置文件。clean-webpack-plugin:每次编译前自动清空dist目录html-webpack-plugin
:从html模板自动生成最终的html,它的AutoWebPlugin插件可以生成多页面应用defineplugin
:声明process.env.NODE_ENV的值extract-text-webpack-plugin
:从js提取css到css文件optimize-css-assets-webpack-plugin
:合并相同的css样式文件css-split-webpack-plugin
:拆分过大的csss文件mini-css-extract-plugin
:分开打包css文件webpack-md5-hash
:webpack自身生成文件hash值的方法是不确定的,为了确保hash值是根据文件内容生成的,可以使用该插件resolve.extensions
:使用require或import引入文件时可以省略后缀:resolve.alias
:简化引用路径:resolve.mainFields
:通过第三方插件的package.json中main字段(大多数第三方模块都采用这个字段)中的地址来引用文件resolve.modules
:限制引入第三方包的范围resolve.mainFiles
:在一定范围下默认优先查找的文件名因为webpack的知识点很多也很细,推荐阅读《深入浅出webpack》。
这本书系统性,渐进性地讲了webpack的基础配置,项目实战,打包优化,插件及运行原理。如果想快速学习webpack并入门可以看看这本书,会比官网看起来更加有重点一些,但注意的是有些配置会有点过时了。
然后初学者可以多上手试试,这样印象更深刻也更能理解,我也在学习中~
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.