最近由于用着html-webpack-plugin觉得很不爽,于是乎想自己动手写一个插件。原以为像gulp插件一样半天上手一天写完,但令人郁闷的是完全找不到相关的文章。一进官方文档却是被吓傻了。首先是进入how to write a plugin看了一页简单的介绍。然后教程会告诉你,你需要去了解compiler和compilation这两个对象,才能更好地写webpack的插件,然后作者给了github的链接给你,让你去看源代码,我晕。不过幸好最后给了一个plugins的API文档,才让我开发的过程中稍微有点头绪。
how to write a plugin这个教程还是可以好好看看的,尤其是那个simple example,它会教你在compilation的emit事件或之前,将你需要生成的文件放到webpack的compilation.assets里,这样就可以借助webpack的力量帮你生成文件,而不需要自己手动去写fs.writeFileSync。
主要就是这段代码
compilation.assets['filelist.md']={
source: function {
return filelist;
},
size: function {
return filelist.length;
}
};
首先,定义一个函数func,用户设置的options基本就在这里处理。
其次,需要设一个func.prototype.apply函数。这个函数是提供给webpack运行时调用的。webpack会在这里注入compiler对象。
输出complier对象,你会看到这一长串的内容,初步一看,我看出了两大类(有补充的可以告诉我)。一个webpack运行时的参数,例如_plugins,这些数组里的函数应该是webpack内置的函数,用于在compiltion,this-compilation和should-emit事件触发时调用的。另一个是用户写在webpack.config.js里的参数。隐约觉得这里好多未来都可能会是webpack暴露给用户的接口,使webpack的定制化功能更强大。
Compiler {
_plugins:
{ compilation: [ [Function], [Function], [Function], [Function] ],
'this-compilation': [ [Function: bound ] ],
'should-emit': [ [Function] ] },
outputPath: '',
outputFileSystem: null,
inputFileSystem: null,
recordsInputPath: null,
recordsOutputPath: null,
records: {},
fileTimestamps: {},
contextTimestamps: {},
resolvers:
{ normal: Tapable { _plugins: {}, fileSystem: null },
loader: Tapable { _plugins: {}, fileSystem: null },
context: Tapable { _plugins: {}, fileSystem: null } },
parser:
Parser {
_plugins:
{ 'evaluate Literal': [Object],
'evaluate LogicalExpression': [Object],
'evaluate BinaryExpression': [Object],
'evaluate UnaryExpression': [Object],
'evaluate typeof undefined': [Object],
'evaluate Identifier': [Object],
'evaluate MemberExpression': [Object],
'evaluate CallExpression': [Object],
'evaluate CallExpression .replace': [Object],
'evaluate CallExpression .substr': [Object],
'evaluate CallExpression .substring': [Object],
'evaluate CallExpression .split': [Object],
'evaluate ConditionalExpression': [Object],
'evaluate ArrayExpression': [Object],
'expression Spinner': [Object],
'expression ScreenMod': [Object] },
options: undefined },
options:
{ entry:
{
'index': '/Users/mac/web/src/page/index/main.js' },
output:
{ publicPath: '/homework/features/model/',
path: '/Users/mac/web/dist',
filename: 'js/[name].js',
libraryTarget: 'var',
sourceMapFilename: '[file].map[query]',
hotUpdateChunkFilename: '[id].[hash].hot-update.js',
hotUpdateMainFilename: '[hash].hot-update.json',
crossOriginLoading: false,
hashFunction: 'md5',
hashDigest: 'hex',
hashDigestLength: 20,
sourcePrefix: '\t',
devtoolLineToLine: false },
externals: { react: 'React' },
module:
{ loaders: [Object],
unknownContextRequest: '.',
unknownContextRecursive: true,
unknownContextRegExp: /^\.\/.*$/,
unknownContextCritical: true,
exprContextRequest: '.',
exprContextRegExp: /^\.\/.*$/,
exprContextRecursive: true,
exprContextCritical: true,
wrappedContextRegExp: /.*/,
wrappedContextRecursive: true,
wrappedContextCritical: false },
resolve:
{ extensions: [Object],
alias: [Object],
fastUnsafe: ,
packageAlias: 'browser',
modulesDirectories: [Object],
packageMains: [Object] },
plugins:
[ [Object],
[Object],
[Object],
[Object],
NoErrorsPlugin {},
[Object],
[Object] ],
devServer: { port: 8081, contentBase: './dist' },
context: '/Users/mac/web/',
watch: true,
debug: false,
devtool: false,
cache: true,
target: 'web',
node:
{ console: false,
process: true,
global: true,
setImmediate: true,
__filename: 'mock',
__dirname: 'mock' },
resolveLoader:
{ fastUnsafe: ,
alias: {},
modulesDirectories: [Object],
packageMains: [Object],
extensions: [Object],
moduleTemplates: [Object] },
optimize: { occurenceOrderPreferEntry: true } },
context: '/Users/mac/web/' }
除此以外,compiler还有一些如run, watch-run的方法以及compilation, normal-module-factory对象。我目前用到的,主要是compilation。其它的等下一篇有机会再说。
对比起compiler还有compiler.plugin函数。这个相当于是插件可以进行处理的webpack的运行中的一些任务点,webpack就是完成一个又一个任务而完成整个打包构建过程的。如make是最开始的起点, complie就是编译任务点,after-complie是编译完成,emit是即将准备生成文件,after-emit是生成文件之后等等,前面几个都是比较生动形象的任务点。
至于compilation,它继承于compiler,所以能拿到一切compiler的内容(所以你也会看到webpack的options),而且也有plugin函数来接入任务点。在compiler.plugin('emit')任务点输出compilation,会得到大致下面的对象数据,因为实在太长,我只保留了最重要的assets部份:
assetsCompilation {
assets:
{ 'js/index/main.js':
CachedSource {
_source: [Object],
_cachedSource: undefined,
_cachedSize: undefined,
_cachedMaps: {} } },
errors: ,
warnings: ,
children: ,
dependencyFactories:
ArrayMap {
keys:
[ [Object],
[Function: MultiEntryDependency],
[Function: SingleEntryDependency],
[Function: LoaderDependency],
[Object],
[Function: ContextElementDependency],
values:
[ NullFactory {},
[Object],
NullFactory {} ] },
dependencyTemplates:
ArrayMap {
keys:
[ [Object],
[Object],
[Object] ],
values:
[ ConstDependencyTemplate {},
RequireIncludeDependencyTemplate {},
NullDependencyTemplate {},
RequireEnsureDependencyTemplate {},
ModuleDependencyTemplateAsRequireId {},
AMDRequireDependencyTemplate {},
ModuleDependencyTemplateAsRequireId {},
AMDRequireArrayDependencyTemplate {},
ContextDependencyTemplateAsRequireCall {},
AMDRequireDependencyTemplate {},
LocalModuleDependencyTemplate {},
ModuleDependencyTemplateAsId {},
ContextDependencyTemplateAsRequireCall {},
ModuleDependencyTemplateAsId {},
ContextDependencyTemplateAsId {},
RequireResolveHeaderDependencyTemplate {},
RequireHeaderDependencyTemplate {} ] },
fileTimestamps: {},
contextTimestamps: {},
name: undefined,
_currentPluginApply: undefined,
fullHash: 'f4030c2aeb811dd6c345ea11a92f4f57',
hash: 'f4030c2aeb811dd6c345',
fileDependencies: [ '/Users/mac/web/src/js/index/main.js' ],
contextDependencies: ,
missingDependencies: }
assets部份重要是因为如果你想借助webpack帮你生成文件,你需要像官方教程how to write a plugin在assets上写上对应的文件信息。
除此以外,compilation.getStats这个函数也相当重要,能得到生产文件以及chunkhash的一些信息,如下:
assets{ errors: ,
warnings: ,
version: '1.12.9',
hash: '5a5c71cb2accb8970bc3',
publicPath: 'xxxxxxxxxx',
assetsByChunkName: { 'index/main': 'js/index/index-4c0c16.js' },
assets:
[ { name: 'js/index/index-4c0c16.js',
size: 453,
chunks: [Object],
chunkNames: [Object],
emitted: undefined } ],
chunks:
[ { id: 0,
rendered: true,
initial: true,
entry: true,
extraAsync: false,
size: 221,
names: [Object],
files: [Object],
hash: '4c0c16e8af4d497b90ad',
parents: ,
origins: [Object] } ],
modules:
[ { id: 0,
identifier: 'multi index/main',
name: 'multi index/main',
index: 0,
index2: 1,
size: 28,
cacheable: true,
built: true,
optional: false,
prefetched: false,
chunks: [Object],
assets: ,
issuer: null,
profile: undefined,
failed: false,
errors: 0,
warnings: 0,
reasons: },
{ id: 1,
identifier: '/Users/mac/web/node_modules/babel-loader/index.js?presets=es2015&presets=react!/Users/mac/web/src/js/main/index.js',
name: './src/js/index/main.js',
index: 1,
index2: 0,
size: 193,
cacheable: true,
built: true,
optional: false,
prefetched: false,
chunks: [Object],
assets: ,
issuer: 'multi index/main',
profile: undefined,
failed: false,
errors: 0,
warnings: 0,
reasons: [Object],
source: '' // 具体文件内容}
],
filteredModules: 0,
children: }
这里的chunks数组里,是对应会生成的文件,以及md5之后的文件名和路径,里面还有文件对应的chunkhash(每个文件不同,但如果你使用ExtractTextPlugin将css文件独立出来的话,它会与require它的js入口文件共享相同的chunkhash),而assets.hash则是统一的hash,对每个文件都一样。值得关注的是chunks里的每个文件,都有source这一项目,提供给开发者直接拿到源文件内容(主要是js,如果是css且使用ExtractTextPlugin,则请自行打印出来参考)。
接下来,会以最近我写的一个插件html-res-webpack-plugin作为引子,来介绍基本的写插件原理。插件的逻辑就写在index.js里。
首先,将用户输入的参数在定好的函数中处理,HtmlResWebpackPlugin。
然后,新增apply函数,在里面写好插件需要切入的webpack任务点。目前HtmlResWebpackPlugin插件只用到emit这个任务点,其它几个仅作为演示。
第三步,调用addFileToWebpackAsset方法,写compilation.assets,借助webpack生成html文件。
第四步,在开发模式下(isWatch=true),直接生成html,但在生产模式下(isWatch=true),插件会开始对静态资源(js,css)进行md5或者内联。
第五步,调用findAssets方法是为了通过compilation.getStats拿到的数据,去匹配对应的静态资源,还有找到对应的哈希(是chunkhash还是hash)。
最六步,调用addAssets方法,对静态资源分别做内联或者md5文件处理。内联资源的函数是inlineRes,你会看到我使用了compilation.assets[hashFile].source 及 compilation.assets[hashFile].children[1]._value。前者是针对于js的,后者是针对使用了ExtractTextPlugin的css资源。
最后一步,即是内联和md5完成后,再更新一下compilation.assets中对应生成html的source内容,才能正确地生成内联和md5后的内容。
有兴趣可以试用一下html-res-webpack-plugin这个插件(为什么要写一个新的html生成插件,我在readme里写了,此处不赘述),看看有哪些用得不爽之处。目前只是第一版,还不适合用于生产环境。希望第二版的时候能适用于更多的场景,以及性能更好。到是,我也会写第二篇插件开发文章,将本文还没提到的地方一一补充完整。也欢迎大家在这里发贴,或者指出本人的谬误之处。
018 眼看就要过去了,今年的你相较去年技术上有怎样的收获呢?
不论你是正在自学前端遇到了瓶颈,还是对某些技术熟练掌握但某些还未涉足,都希望这份清单能对你有所帮助。由于头条不让站外链接,可以自行复制来源链接或者文末查看更多
作者:AlienZHOU
来源:
https://github.com/alienzhou/frontend-tech-list
学习文章的知识往往是碎片化的。而前端涉及到的面很广,这些知识如果不进行有效梳理,则无法相互串联、形成体系。因此,我结合工作体会将抽象出了一些前端基础能力,并将看过、写过的一些不错的文章进行整理,形成了一份(纯)前端技术清单。
不论你是正在自学前端,还是对前端某些技术熟练掌握但某些还未涉足,我都希望这份清单能帮助你 review 一些前端的基础能力。
0. 年度报告
1. 基础拾遗
温故而知新,不知则习之,是以牢固根基。
1.1. JavaScript
1.2. CSS
1.3. 浏览器
2. 工程化与工具
软件规模的扩大带来了工程化的需求,前端也不例外。随着 NodeJS 的出现,前端工程师可以使用熟悉的 JS 快速开发所需的工具。工具链生态的繁荣也是前端圈繁荣的一个写照。
2.1. webpack
2.2. Gulp
2.3. Linter
2.4. 静态类型(Typescript/Flow)
2.5. Babel
2.6. CSS预处理与模块化
3. 性能优化
性能优化其实就是在理解浏览器的基础上“因地制宜”,因此可以配合1.3节“浏览器”部分进行理解。
强烈推荐把 Google Web 上性能优化 Tab 中的文章都通读一遍,其基本涵盖了现代浏览器中性能优化的所有点,非常系统。下面也摘录了其中一些个人认为非常不错的篇幅。
3.1. 加载性能
3.2. 运行时性能
3.3. 前端缓存
3.4. 性能调试与实践
3.5. 性能指标
4. 安全
很多安全风险老生常谈,但是往往到出现问题时,才会被重视或者意识到。
4.1. XSS
4.2. CSRF
4.3. CSP
4.4. HTTPS
4.5. 安全实录
5. 自动化测试
自动化测试是软件工程的重要部分之一,但却极容易被忽视。
5.1. 单元测试
5.2. 端到端测试 (E2E)
5.3. 其他
6. 框架与类库
如果说基础知识是道,那框架与工具可能就是术;学习与理解它们,但千万不要成为它们的奴隶。
6.1. React
6.2. Vue
6.3. Redux
6.4. RxJS
7. 新技术/方向
前端领域新技术、新方向层出不穷,这里汇总一些新技术方向;作为开发者需要多了解但是不要盲从
7.1. PWA
7.2. CSS Houdini
7.3. Web Components
7.4. 微前端(Micro Frontends)
7.5. HTTP/2
7.6. WebAssembly
8. 业务相关
在业务中往往还有一些与“业务无关”的场景需求 —— 不论是什么业务几乎都会遇到;因此,在变与不变中,我们更需要去抽象出这些问题。
8.1. 数据打点上报
8.2. 前端监控
8.3. A/B测试
8.4. “服务端推”
8.5. 动效
9. 不归类的好文
开卷有益。
程序开发过程中,我们始终要谨记的一点就是:程序是写给人看的,不是写给机器看的。任何项目开发,都必须要考虑到人员迭代,我们不能让下一个接手你代码的人,在看到你写的代码时会说出这样的话,“这个代码是人写出来的吗?可读性太差了”。因此,我们必须遵循一定的规范,让代码的可读性更强。
今天,我们就一起来看下前端开发过程所能涉及到的跟HTML有关的规范问题。
HTML5
在HTML文件中,推荐使用支持HTML5特性的文档声明,<!DOCTYPE html>。
首先是在文件的命名上,应当采用驼峰式命名,首字母小写,后面每个单词首字母大写,而且对于具体的文件应当具有语义化,能够给人一种直观的感受这个文件的作用是什么。现在前端开发开发过程中都讲求模块化开发,甚至是组件化开发,在文件命名时更应该以模块名或者组件名来命名。
例如在写一个AngularJS应用时,由于会涉及到Controller,Service,Filter等概念,我们会分别建立一个文件,假如这个模块的名字是库存管理stockManage,我们可以这样来命名文件。
stockManageCtrl
stockChangeService
stockChangeFilter
我们所说的语义化指的是使用具有语义化的标签,在H5中添加了类似于header, nav, article, section, aside, footer等标签,从单词的意思上我们也很容易看出标签的含义。
我们不推荐使用只有div标签的页面,例如
不推荐使用
而是应该使用以下这种带有语义化的标签。
推荐使用
img标签是网页用来显示照片的标签,在页面所有标签中占据的比例非常之高,但是在使用img标签时也有下面需要注意的点。
给定width和height属性
因为浏览器在加载图片的过程中,需要先下载图片,然后再解析图片的高度和宽度,如果不给img元素设定高度和宽度,这样在图片加载过程中会不断的计算,重排页面的布局,在网络不好的时候就会经常出现元素出现不规律移动的情况。因此给img元素设定width和height属性是必要的。
alt属性
img标签的alt属性表示的是在图片无法显示时,使用文字来代替显示,它可以用在以下几个场景中:
网路延迟太大
src属性指定路径出错
浏览器禁用图像
由于其有良好的信息提示效果,并且有助于网页SEO效果,强烈建议在img标签中使用alt属性。
而且很重要的一点是img标签的引入是需要呈现出与页面相关的内容,其他情况应该使用CSS样式实现。例如我们不推荐下面这种情况。
不推荐
而推荐使用下面这种情况
推荐使用
前端文件主要包括HTML页面文件,CSS样式文件和Javascript脚本文件。我们应该让三者各司其职,在HTML中不应该出现CSS和JS表达式;在JS文件中,不应该出现大量的HTML和CSS代码。在HTML文档中应当尽量少的引入CSS和JS文件。为了保证文件的纯净,我们应当遵循下面的原则。
一个HTML文件应该只引入一个CSS文件
合理运用JS合并技术(Gulp, Webpack插件),保证引入JS文件不多于两个
不使用行内脚本元素(<script>alert('Hello World')</script>)
不在标签上使用style内联样式
不要使用style属性
脚本加载在网页加载过程中是一个很耗性能的过程,如果把JS文件放在head标签里,它的加载会一直阻塞DOM的解析,造成页面延迟。
因此现在讲求的是脚本的异步加载过程,我们会使用到async关键字,考虑到浏览器的兼容性,我们推荐使用下面的方式加载脚本。
推荐方式
合理使用ID和锚点可以非常方便的实现当前页面间的跳转,现在越来越多的教程网页由于是单页面,经常会用到锚点跳转。
对锚点知识还不了解的,可以看看我写的这篇文章《神奇的html锚点,让你的网页在内部自由的跳转》。
今天这篇文章主要总结了前端开发过程中的HTML规范问题,相信大家也或多或少遇到过,希望这篇文章能加深大家的认识。
*请认真填写需求信息,我们会在24小时内与您取得联系。