关于构建工具:面向多告警源如何构建统一告警管理体系

本文介绍告警对立治理的最佳实际,以帮忙企业更好地解决异构监控零碎所带来的挑战和问题。 背景信息在云原生时代,企业IT基础设施的规模越来越大,越来越多的零碎和服务被部署在云环境中。为了监控这些简单的IT环境,企业通常会抉择应用异构监控零碎,例如Prometheus、Grafana、Zabbix等,以获取更全面的监控数据,以便更好地理解其IT基础设施的运行状况和性能体现。 然而,这种异构监控零碎也带来了一些问题,其中最显着的是告警信息的扩散。因为不同的监控零碎可能会产生不同的告警信息,这些信息可能会扩散在各个系统中,导致企业很难全面理解其IT零碎的告警情况。这使得响应告警变得更加艰难,同时也减少了人工治理的复杂性和工作量。 残缺内容请点击下方链接查看: https://developer.aliyun.com/article/1243892?utm_content=g_10... 版权申明:本文内容由阿里云实名注册用户自发奉献,版权归原作者所有,阿里云开发者社区不领有其著作权,亦不承当相应法律责任。具体规定请查看《阿里云开发者社区用户服务协定》和《阿里云开发者社区知识产权爱护指引》。如果您发现本社区中有涉嫌剽窃的内容,填写侵权投诉表单进行举报,一经查实,本社区将立即删除涉嫌侵权内容。

July 4, 2023 · 1 min · jiezi

关于构建工具:B-站构建实时数据湖的探索和实践

一、背景和痛点在大数据场景利用中,业务不仅要计算数据后果,而且要保障时效性。目前,我司演化出两条链路。时效性高的数据走 Kafka、Flink 实时链路;时效性要求低的数据走 Spark 离线链路。上图简略形容了 B 站数据上报、解决和应用的链路。数据采集次要通过 APP 端上报的行为事件数据。服务端上报的日志数据会通过网关以及散发层,流式散发到大数据数仓体系内。 MySQL 中存储的业务数据,通过 Datax 周期性的批式同步到数仓内。时效性高的数据会通过 Flink+Kafka 进行流式计算。时效性低的数据通过 Spark+HDFS 进行批计算最初出仓到 MySQL Redis Kafka 的介质中,为 AI、BI 的模型训练、报表剖析场景应用。 残缺内容请点击下方链接查看: https://developer.aliyun.com/article/1211753?utm_content=g_10... 版权申明:本文内容由阿里云实名注册用户自发奉献,版权归原作者所有,阿里云开发者社区不领有其著作权,亦不承当相应法律责任。具体规定请查看《阿里云开发者社区用户服务协定》和《阿里云开发者社区知识产权爱护指引》。如果您发现本社区中有涉嫌剽窃的内容,填写侵权投诉表单进行举报,一经查实,本社区将立即删除涉嫌侵权内容。

May 29, 2023 · 1 min · jiezi

关于构建工具:构建集群runner并发数在哪里设置呢

在云效中,构建集群的 Runner 并发数能够在构建集群的设置中进行配置。具体操作步骤如下: 登录云效控制台,进入构建集群页面。 找到须要配置的构建集群,点击其对应的“治理”按钮。 在构建集群的设置页面中,能够看到“Runner 并发数”设置项。 在“Runner 并发数”设置项中,能够设置该构建集群中 Runner 的最大并发数。默认状况下,该值为 1,即每次只能执行一个构建工作。 残缺内容请点击下方链接查看: https://developer.aliyun.com/ask/499531?utm_content=g_1000371149 版权申明:本文内容由阿里云实名注册用户自发奉献,版权归原作者所有,阿里云开发者社区不领有其著作权,亦不承当相应法律责任。具体规定请查看《阿里云开发者社区用户服务协定》和《阿里云开发者社区知识产权爱护指引》。如果您发现本社区中有涉嫌剽窃的内容,填写侵权投诉表单进行举报,一经查实,本社区将立即删除涉嫌侵权内容。

April 28, 2023 · 1 min · jiezi

关于构建工具:构建制品不一致后续工作都是白费

专栏策动|雅纯 意愿编辑|冯朝凯、橙蜂 之前咱们举了《集装箱扭转世界》(作者:马克.莱文森)中的一个例子,书中提到上世纪五六十年代,集装箱的应用,使得整体货运老本升高了95%,大部分的码头工人都面临着就业。 这件事件看起来很简略,但却给经济全球化带来了十分大的影响。前面美国企业的订单能够下到中国、以及中国成为“世界工厂”,都与之有很大的关系。集装箱的背地是标准化和基于统一标准的产业链,这里有两点比拟重要的,一个是标准化,另外一个是不可变。 那么,在软件开发的过程中,咱们怎样才能享受产业生态的红利,实现软件交付过程的标准化呢?软件交付当中的集装箱应该是什么样的? 如何保障软件交付过程的标准化近十几年,软件交付状态产生了很大的变动,从最开始买物理机、建机房到虚拟机再到当初的容器。这两头为什么会产生这样的变动呢? 容器自身的底层技术是namespace和cgroup,然而这两个货色在十几二十年前就呈现了。最早利用这些技术的是对资源利用率和隔离有明确诉求的云厂商,比如说阿里云不心愿跑在机器上不同用户的货色相互串,最好的方法就是能限度每个用户的资源,如CPU、内存等。有了这个诉求,就会用LXC等形式去隔离,去限度资源。然而这还是没有产生容器。为什么呢?问题是各个云厂商只能在本人外部做,然而不能对外散发。所以Docker的平凡之处并不是在底层做了多大地翻新,而是提供了一个能够对外散发的容器镜像。 容器镜像是一个散发的模式,咱们能够把容器镜像分发给他人,或者是让他人继承咱们的镜像。同时Docker又提供了Dockerfile。Dockerfile容许咱们通过一个文件的模式去形容镜像。一旦可能定义镜像就能够合作了。有了这样的能力当前,容器就很快被大家所承受了。所以容器的承受看起来如同是技术倒退的过程,其实是随着云原生、云市场的倒退必然带来的后果。 咱们很多人认为的“集装箱”就是容器,这个容器很多时候咱们都认为是docker容器。在K8s外面反对很多个容器运行,大部分的状况都是用docker容器。docker容器的劣势就是方才说的两点:镜像和Dockerfile。这两点使得docker镜像能够像集装箱一样做散发。 此外,容器还提供了很好的资源隔离,能够在比拟小的粒度上进行隔离。虚拟机尽管也做了隔离,然而它的粒度比拟大。不仅如此,容器还提供了十分弹性的资源管理形式,这点比虚拟机和物理机都有十分大的改善。实质上它就是物理机上的一个过程,这是它和虚拟机的实质的差异。 理解了软件集装箱是什么后,而后咱们再来理解下容器镜像的组成。 如上图所示,这张图十分形象地展现了容器镜像的内部结构。当咱们本人执行dockerbuild构建镜像的时候,你会发现它进去的日志有很多hash值,一层一层的。实际上它是由很多层组成的,咱们通过LXC或者其余的技术,把容器的过程创立进去,这个过程通过namespace和cqroup做了资源隔离和限度。容器镜像都有一个BaseImage,咱们晓得运行一个程序,对操作系统的环境是要求的,比方依赖的library等。这个程序如果轻易在一台物理机和虚拟机部署,会随着机器的环境不同而不同,有可能导致危险。所以容器镜像给了一个根本镜像,把这个货色放外面了。再往上是Addemacs和Addapache,这两层咱们会在Dockerfile中去写。而后最下面的是Writable,就是咱们在容器Container运行的时候真正能够去写的货色。 那么容器镜像的特点是什么呢?它是分层的,每一层都是能够复用的,咱们在某个机器上有很多个容器,如果Base镜像一样,只有下一次就行了。能够看到镜像的大小是所有的层堆起来的,堆的货色越少,这个镜像就会越小。容器镜像有一个最小的镜像叫scratch,就是一个最原始的根底镜像。这外面简直什么都没有,基于它构建一个十分十分小的容器的话,可能就是几兆的大小。然而如果你基于CentOS根底镜像可能就是上G的大小。 容器镜像有一个十分重要的概念叫“One process per container”(容器生命周期=过程生命周期)。咱们能够认为容器就是K8s上的一个过程,如果把K8s比作操作系统,那么容器就是它下面运行的一个过程。过程的生命周期是能够被治理的。尽管容器有这么多的长处,但理论在用的时候也会遇到很多的问题。 上面咱们聊一聊容器镜像的一些常见的问题和倡议。 容器镜像常见问题及实际倡议容器镜像常见的问题: 把所有的货色都装到一个容器外面,把容器当虚拟机来用。把ENTRYPOINT设置为systemd:systemd治理的过程运行的后果和状态和的容器状态是不统一的,有可能外面的过程曾经僵死了,或者Crash了,然而systemd还活着,从内部看起来这个容器没问题。私有化部署的时候带一堆导出的镜像tar包。tar包是不分层的,它不晓得外面是有很多层。每次把根底镜像下发到整个集群,导致网络变得特地拥挤咱们的实际倡议是: 尽量采纳轻量的根底镜像和确定的镜像版本。通过分层来复用镜像内容,防止反复拉取。防止采纳systemd,包含supervisord和相似这样的daemon治理服务来做ENTRYPOINT。采纳本地的dockerregistry等以层为粒度来离线拷贝镜像。防止同时要做大量的pull,可采纳P2P的形式(如应用dragonfly)晋升镜像散发效率。容器镜像能够实现软件交付过程的标准化。标准化是伎俩不是目标,标准化是帮忙咱们更高效的复用的技术。 回到软件交付的终态,咱们的目标是心愿提供一个稳固可预期的零碎。 而达成这个指标的前提是,要有确定的运行环境和软件制品。确定的环境是指代码(及其依赖)、构建环境、构建脚本与预期统一的产出软件制品,这一点如何做到咱们前面再作分享。咱们先看如何保障软件制品的一致性。 如何保障软件制品的一致性要保障软件制品的一致性,软件制品应该有确定的格局、惟一的版本、可能追溯到源码、可能追溯到生产和生产过程,这样能力使继续交付更好地服务于企业的制品治理与开发。 在制品构建过程中,常常会遇到一些问题。例如利用的代码库里没有Makefile,package.json,go.mod而没法确定依赖,或者制品能构建胜利但缺失几个依赖,又或是在本人的开发环境运行失常而在生产环境呈现了开发环境没有的bug。导致这些问题呈现的起因是因为构建自身是可变的,当你构建可变时,就会带来一系列的问题。为此,咱们须要通过不可变构建来使制品与预期统一。 要实现不可变构建,咱们须要保障有: 雷同的代码雷同的构建环境雷同的构建脚本雷同的代码 例如程序员开发时,不在依赖形容文件(如go.mod,package-lock.json,pom.xml,requirements.txt等)中指定依赖的版本,则会默认应用最新的版本作为依赖,这样产出的制品会随着依赖的更新而不能保持一致,这将带来齐全不在预期内的危险。 雷同的构建环境 对于构建环境来说,Dockerfile能够用来在容器平台下形容环境,通过Dockerfile咱们能为制品应用统一的环境。很多时候咱们并不需要在运行中应用构建环境的很多依赖,而构建镜像的体积往往比拟惊人,这个时候咱们就须要将构建环境与运行环境离开,以失去尽可能轻量的镜像制品。 雷同的构建脚本 对应的,应用雷同的,与代码实现无关的构建脚本也是十分重要的,在Dockerfile的环境中必须指定确定的环境依赖版本。 只有在同一份代码(及同一个依赖)、同样构建环境的形容、和同样构建脚本的环境下,所产生的软件制品才是雷同的。这里强调的是说所有的货色都要保障一致性,如果说三者是一样的话,那产生进去的制品也是一样的,即便构建工夫不同,产出的制品也是雷同的。 做好不可变基础设施,首先要标准化最终交付制品的状态,并且明确此交付状态的运维治理形式。而要保障不可变,那首先要做好不可变的构建,而后能力有统一的软件制品。 NOTE:构建准确性,永远比构建更快重要。制品的构建信息不精确,导致构建制品不统一、版本不可控,所有后续的工作都是节约。 如何晋升构建效率在构建这块,一个须要关注的点的是如何晋升构建效率。咱们先看一个简略的计算问题: 这是一个十分大的数据,也是十分大的损耗。很多时候一个我的项目的工程效率太低的起因就是因为构建太慢。构建耗时过长使得制品迭代十分慢,性能更新和bug修复也会受到影响。 那咱们如何晋升构建的效率呢?上面是咱们的一些实际倡议: 1个根本准则: 保障构建的准确性,构建的准确性永远优于构建的效率。只有在保障准确性的前提下晋升效率才有意义。 5点倡议: 利用瘦身:查看利用的依赖状况,利用包体积是否过大,依赖项是否过多,是否去除不必要的依赖,是否构建更小的镜像。分层构建:底层的货色先构建进去当前被下层所复用,而后就能够做增量式的了。构建缓存:构建过程中拉取依赖是很耗时的,要防止反复拉取。网络优化:次要是保障代码、构建机器和制品库之间的低网络延时。代码和构建机器是否是在同一个低时延链路中。例如代码在Github上而应用云效构建,此时的延时绝对于内网会高出许多。仓库镜像:仓库镜像能够极大地缩小拉取依赖项的工夫。在国内的网络环境下,如果从源仓库获取依赖,可能延时会十分长,这时能够应用镜像网络升高延时。例如nodejs开发者常应用淘宝的npm镜像源,而Python开发者应用清华的镜像源。对于企业来说也能够构建本人的镜像仓库以晋升可靠性与升高延时。云效也应用了镜像仓库,来缩小拉取的工夫。(小编举荐:云效流水线Flow 是一款云原生时代的流水线工具,通过容器技术让企业解脱对虚拟机构建环境的依赖。您甚至能够依据您的应用需要,在同一条流水线上应用不同的构建环境。此外,云效流水线Flow 还提供了各种语言的容器环境,满足不同的构建应用场景。点击文末浏览原文,理解详情)总结本篇文章,咱们从软件交付的终态登程,提出了不可变构建的概念。心愿通过:雷同的源码+雷同的环境+雷同的构建脚本=>带来统一的软件制品。而这些货色都是保留在源代码里的,所以源代码的治理十分重要。 下篇文章,咱们将分享如何对源代码进行无效治理。 浏览上篇:做到这4点,才是真正的继续交付 点击下方链接立刻体验云效继续交付流水线Flow。 https://www.aliyun.com/product/yunxiao/flow?channel=yy_rccb_36

February 9, 2022 · 1 min · jiezi

关于构建工具:如何通过云效Flow完成自动化构建构建集群

如何通过云效Flow实现自动化构建—构建集群,云效流水线Flow是继续交付的载体,通过构建自动化、集成自动化、验证自动化、部署自动化,实现从开发到上线过程的继续交付。通过继续向团队提供及时反馈,让交付过程高效顺畅,云效Flow为了反对用户个性化的自动化构建。 云效构建集群,云效Flow 反对用户个性化的构建场景,提供三种类型的构建集群的能力反对。用户在流水线编排时,能够为工作设置不同的构建集群。北京和香港构建集群 北京和香港构建集群为解决用户下载海内构建依赖或者连贯海内服务的场景需要,Flow 别离提供了北京构建集群和香港构建集群。 应用构建集群 在工作节点抉择构建集群,默认应用北京构建集群。 自有构建集群如果有非凡的构建环境需要,用户也可在 Flow 中创立并应用公有的构建集群。 云效流水线 Flow 反对应用用户阿里云 ECS 或者自有主机作为构建集群。 对于上述的主机类型,须要确认以下配置: 反对的 OS 类型:Linux举荐配置:4 核 8G 或以上装置并启动 Docker Daemon:https://docs.docker.com/engine/install/验证 Docker 运行状态:在主机上执行命令 sudo docker ps; echo $?,输入应该为 0对于应用阿里云ECS主机作为构建集群的状况,须要确保云助手处于运行状态。创立自有构建集群 通过 构建集群治理 -> 新建构建集群,开始创立自有构建集群。 你能够创立两种类型的主机组:阿里云 ECS 构建集群自有主机构建集群 应用自有构建集群 在工作节点抉择构建集群,切换为自有构建集群。 图2.应用构建集群 构建语言反对云效流水线 Flow 通过多种构建步骤,反对不同语言的构建能力。上面形容构建环境中装置的软件清单,及不同语言场景下的应用办法。 公共软件所有构建环境都蕴含的软件: gcc/g++ 4.8.4make 3.8.1curl 7.35.0wget 1.15unzip 6.00git 1.9.1python 3.5(不在PATH中,须要应用/usr/alibaba/install/python-3.5.0/bin/python3来援用)各语言构建环境中的软件及应用办法 对应的构建能力都是通过根底构建机反对,根底环境曾经反对了上述构建能力,如上述能力无奈反对你的构建需要,请你分割咱们的客服以获取更多的反对。 设置依赖下载拜访凭证环境变量 在构建过程中,须要设置仓库的拜访凭证并下载构建依赖,为爱护拜访凭证的安全性,Flow 反对通过环境变量设置拜访凭证,对于环境变量请查看:环境变量 当然,作为企业对立管控的仓库拜访凭证,咱们更建议您应用通用变量组进行治理,对于通用变量组请查看:通用变量组 以下,提供了如何应用环境变量配置 Java、NPM 和 GoLang 仓库拜访凭证的指南。 Java 1、在settings.xml中增加私库的地址和账号/明码(环境变量)。 ...

October 9, 2021 · 1 min · jiezi

关于构建工具:利用增量构建工具-Preset-打造自己的样板库

你是如何开始一个我的项目呢?是基于以后技术栈提供的脚手架还是从 npm init 开始呢? 以前我没得选,必须面向搜索引擎。基于 webpack 或 rollup 来一步步构建我的项目,在开发过程中还有可能产生很多谬误。但当初我只想专一于以后业务,筛选适合的脚手架之后迅速构建本人的我的项目,这样的话,就能够把大量维护性的工作交给开源作者。 当然,出名的脚手架工具(Vue CLI,Umi,Vite 等)自不必说,这里我举荐几个棘手的工具。 microbundle-crl 专一于 React 组件的构建tsdx 专一于 TypeScript 库的构建crateApp 依据以后选项配置生成我的项目包 (多个根底构建工具 Webpack,Parcel,Snowpack)但无论是哪一个样板库或者脚手架,都不会完全符合以后业务的需要,开发者须要基于以后的样板进行批改。比如说须要在我的项目中要增加开源协定,批改项目名称,以及为我的项目增加不同的依赖。 从构建来说,目前有两个问题: 大量重复性操作如果生成我的项目的工作频率很高的话,例如一周写一个业务性组件。尽管每次在我的项目中要增加开源协定,批改项目名称,增加特定依赖都是一些小活,但频率高起来也是一件麻烦的事件。 底层依赖无奈间接降级如果开发者批改了以后样板,那么脚手架呈现破坏性更新时候就无奈间接降级(这种问题当然也比拟少)。尽管开发过程中会记录一些批改。但随着工夫的偏移,开发者不会确切晓得须要编辑或删除哪些文件能力使降级后的我的项目失常工作。 话不多说,咱们来看一看工具 Preset 是如何解决这一系列的问题的。 应用 Preset首先建设一个我的项目,以 vite 为例子,package.json 如下所示 { "name": "vite-preset", "version": "0.0.1", "author": "jump-jump", "license": "MIT", "preset": "preset.ts", "prettier": { "printWidth": 80, "tabWidth": 2, "trailingComma": "all", "singleQuote": true, "arrowParens": "always", "useTabs": false, "semi": true }, "devDependencies": { "apply": "^0.2.15" }}执行上面的操作,咱们会的到 my-vue-app 文件。 # npm 6.xnpm init @vitejs/app my-vue-app --template vue拿到了以后命令生成的后果之后咱们把以后生成文件拷贝到 vite-preset 根目录下的 templates 中(即 templates/vite ) 文件夹下。 ...

May 21, 2021 · 3 min · jiezi

关于构建工具:vuecli-迁移-vite2-实践小结

两周前(202.02.17),vite2.0 公布了,作为应用了浏览器原生 ESM 为下一代前端工具,vite 2.0 相较于 1.0 更加成熟。在此之前笔者就开始关注这类「新型」的前端工具。这次趁着 vite 2.0 公布,也胜利将一个基于 vue-cli(-service) + vue2 的已有我的项目进行了迁徙。 迁徙工作比较顺利,花了不到半天工夫。但整个迁徙过程中也遇到了一些小问题,这里汇总一下,也不便遇到相似问题的敌人一起交换和参考。 我的项目背景在介绍具体迁徙工作前,先简略介绍下我的项目状况。目前该我的项目上线不到一年,不太有构建相干的历史遗留债权。我的项目蕴含 1897 个模块文件(包含 node\_modules 中模块),应用了 vue2 + vuex + typescript 的技术栈,构建工具应用的是 vue-cli(webpack)。算是一套比拟规范的 vue 技术栈。因为是外部零碎,我的项目对兼容性的要求较低,用户根本都应用较新的 Chrome 浏览器(少部分应用 Safari)。 迁徙工作上面具体来说下迁徙中都做了哪些解决。 1、配置文件首先须要装置 vite 并创立 vite 的配置文件。 npm i -D vitevue-cli-service 中应用 vue.config.js 作为配置文件;而 vite 则默认会须要创立一个 vite.config.ts 来作为配置文件。根底的配置文件很简略: import { defineConfig } from 'vite';export default defineConfig({ plugins: [ // ... ],})创立该配置文件,之前的 vue.config.js 就不再应用了。 2、入口与 HTML 文件在 vite 中也须要指定入口文件。但和 webpack 不同,在 vite 中不是指定 js/ts 作为入口,而是指定理论的 HTML 文件作为入口。 ...

March 9, 2021 · 4 min · jiezi

深入浅出webpack有感

对于前端仔来说,相信大家对webpack都再熟悉不过了,但是你对webpack的了解程度又有多深呢,笔者花了几天时间看了一下《深入浅出webpack》,虽然说书中大部分介绍的是配置和使用相关的,但是如果你对webpack的配置、使用、原理和构建流程更加熟悉的话,对于你的开发可以说是百里无一害!本文不会局限于介绍配置,也不会详细介绍打包原理(后面打算写一篇有关webpack打包原理的~),更多着重于webpack打包的思想介绍。没有打包构建的日子nodejs的出现对于构建工具具有重要的意义,在没有nodejs之前,js只能执行在浏览器环境下,所以意味着对发布前的js文件要进行处理,十分局限,没有打包工具,只能用PHP脚本来处理文件,甚至还需要借助一些在线压缩网站,开发体验十分差劲,在史前时代存在以下几个痛点:1、缺乏文件处理工具,对文件进行编译或其他预处理,进行打包压缩等工作;2、缺乏文件的模块化,引入第三方库直接用cdn引入,需要处理依赖管理,人为控制脚本的加载顺序,并且存在全局变量命名冲突的问题;3、缺乏代码校验和自动化测试,在代码被提交到仓库前需要校验代码是否符合规范,以及单元测试是否通过。 有了打包构建工具的日子随着nodejs的诞生,我们可以在开发环境下书写nodejs代码脚本,对我们的前端代码做预处理,编译压缩等工作,最初诞生的是grunt和gulp,Grunt和Gulp都属于任务流工具Tast Runner,两者都是通过配置好配置文件,但是相比之下,gulp通过函数式编写配置文件,以及前端人员所熟悉的链式调用,让大家觉得更易懂更易上手,gulp本身借鉴了grunt的经验进行升级和加入一些新特性。正因为流管理多任务配置输出方式的提高,人们逐渐选择使用Gulp而放弃grunt。有了grunt和gulp,文件压缩处理的工作解决了,代码校验和测试也可以处理了,但是模块化仍没有结果?其实前端的痛点还远不止模块化那么简单,频繁的DOM节点处理,JS里杂糅了交互逻辑、请求逻辑、数据处理和校验逻辑、DOM操作逻辑,导致JQ书写的代码就更意大利炒大便,呸!意大利炒面一样。在团队开发中,可能你的代码要给别人维护,这就非常痛苦了。 webpack诞生记1、模块化思想隔离不同的js文件,模块化开发,仅暴露当前模块所需要的其他模块,这是模块化思想想要传递给我们的。nodejs诞生后,后端所采取的模块化思想是commonjs,然而,不同于后端,前端的代码运行在浏览器端,有两点不同之处:1、没有nodejs执行环境,不支持module.exports的书写格式;2、后端require一个文件,是读取本地文件的形式,速度极快,而对于前端而言,需要去动态加载一个js文件,存在额外耗时。于是AMD思想应运而生,对此的相应实现是requireJS,允许你定义好模块名称、模块依赖以及当前的模块代码(function),通过广度优先遍历的方式,递归加载父模块所依赖的子模块,但是这也暴露出了一些问题:1、通过js加载执行后再去加载其依赖的子模块,这个递归加载过程本身是耗时的;2、模块化思想提倡我们分隔逻辑,管理好各个js文件内的逻辑,一旦分割的JS文件过多,最终造成前端资源加载压力。不过不用担心,requireJS提供了r.js来处理发布前的模块合成,帮助你把多个JS文件打包成一个文件。再到后来,国内出现了CMD的思想,不同于AMD的声明依赖的形式,允许你动态加载依赖,但是其实现以及具体的运行结果被大家诟病。再到后后来,为了解决这种不同库的模块实现不一致的问题,提出了UMD,其实很简单,只是写一段hack,让你的模块能够兼容不同的模块加载场景,无论是commonjs还是AMD,如果都没有的话就直接声明为一个全局变量的形式。下面引用一段UMD的代码: (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = global || self, global.Vue = factory());}(this, function () { 'use strict'; //your code here}再到后后后来,未来的大一统,es6中所提出的import和export的形式来统一前后端的模块化加载方式。相比以往的实现,import/export的形式,在模块化加载JS文件的时候,保留动态的执行引用,其次,不允许动态控制加载依赖,使得tree-shaking成为可能。 2、组件化思想组件化的思想并非前端所特有,在客户端也会面临相同的问题。想象一下,A跟B被同时分配一起开发完成一个首页页面,包括导航栏、轮播图、网站列表数据,登录框等,两人需要如何分工协作?导航栏、列表这种需要在多个页面复用的HTML怎么办?假如在没有组件化处理的情况下:1、A和B分工困难麻烦,代码提交时会处理大量的冲突;2、导航栏、列表等多处复用的地方,需要cv大法直接复制粘贴到另一个页面中去使用。组件化思想,让我们把页面划分为一个个组件,组件内部维护自己的UI展示、交互逻辑,组件间可以进行数据通信,实现一种变相的相互隔离,便不会出现A和B两人一起编辑一段html的难受场景,同时,提高了代码的可维护性和复用性,这是其解决的关键痛点。引用vue官网的一张有关组件化思想的图: 3、MVC框架、MVVM框架的流行在模块化和组件化的基础上,实现了页面组件之间的隔离和各自维护,但是面临的最大的一个痛点问题,仍然是前面所说的,前端的JS逻辑中杂糅了各种处理逻辑,交互逻辑、请求逻辑、数据处理和校验逻辑、DOM操作逻辑;而其实这一切可以划分为两个层次,一个是数据层,一个是视图层,如何避免重复的书写操作DOM的逻辑,如果说早期的各种模板引擎给了我们初期的解决方案,那么vue、react以及angular就是在模板引擎的基础上的上层建筑。为了让我们更加专注于数据的处理,MVC框架和MVVM框架帮我们做了以下两件事:1、监听页面操作事件,触发相应的事件钩子,执行代码逻辑,即V层到M层的过程;2、执行代码逻辑后,数据层发生修改,帮我们更新渲染页面,即M层到V层的过程;vue中通过vm实现,react中通过触发setState通知。如此,我们只需要书写一次html,在html中写明绑定或展示的数据,同时绑定好事件监听器,后续便不需要再处理视图层相关的操作,只需要关注于自己的业务逻辑代码、数据层的处理等。MVVM架构流程图: 4、代码打包构建前面介绍了grunt、gulp打包构建工具,其实webpack本质也是打包构建工具,但是webpack呈现出来的功能更为强大和成熟。对于代码的预处理、模块化加载、代码分割等,webpack具有更大的优势。 webpack诞生! 读者读到这里,可能仍有些许疑惑,前面讲了这么多,为啥还是没有介绍到webpack相关的,其实不然,仔细回想一下前面所介绍的思想,以及你平时使用webpack来打包构建的时候,其实webpack正是帮你处理了这些繁杂琐碎的事情。如果代码预处理压缩足以,那么grunt和gulp已经满足了;如果说模块化开发足以,那么requireJS和Browserify已经满足了;如果说组件化开发、MV*框架足以,那么只需要在页面内引入相应的vue或react框架,足矣。笔者写这边文章,更多是想让大家能够思考工具或者框架背后,所呈现出来的思想,webpack就像是一个巨无霸,集大成者,它解决了打包构建,它处理了模块化开发,它帮助你和其他框架完美融合实现组件化开发;而这几年来MV*框架的流行对于webpack市场的迅速扩展有着不小的贡献。 webpack为我们做了以下这些事:(引自《深入浅出webpack》)代码转换:TypeScript 编译成 JavaScript、SCSS 编译成 CSS 等。 文件优化:压缩 JavaScript、CSS、HTML 代码,压缩合并图片等。 代码分割:提取多个页面的公共代码、提取首屏不需要执行部分的代码让其异步加载。 模块合并:在采用模块化的项目里会有很多个模块和文件,需要构建功能把模块分类合并成一个文件。 自动刷新:监听本地源代码的变化,自动重新构建、刷新浏览器。 代码校验:在代码被提交到仓库前需要校验代码是否符合规范,以及单元测试是否通过。 ...

October 8, 2019 · 1 min · jiezi

CMake指南

版权申明:本文原创首发于以下网站,您可以自由转载,但必须加入完整的版权声明博客园:https://www.cnblogs.com/Mogoo... csdn博客:https://blog.csdn.net/nmjkl001/ 知乎:https://www.zhihu.com/people/... 简书:https://www.jianshu.com/u/954... segmentfault:https://segmentfault.com/u/mo...CMakeLists.txt 文件解析cmake verson,指定cmake版本 cmake_minimum_required(VERSION 3.2)project name,指定项目的名称,一般和项目的文件夹名称对应 PROJECT(test_sqrt)head file path,头文件目录 INCLUDE_DIRECTORIES( include )source directory,源文件目录 AUX_SOURCE_DIRECTORY(src DIR_SRCS)set environment variable,设置环境变量,编译用到的源文件全部都要放到这里,否则编译能够通过,但是执行的时候会出现各种问题,比如"symbol lookup error xxxxx , undefined symbol" SET(TEST_MATH ${DIR_SRCS} )add executable file,添加要编译的可执行文件 ADD_EXECUTABLE(${PROJECT_NAME} ${TEST_MATH})add link library,添加可执行文件所需要的库,比如我们用到了libm.so(命名规则:lib+name+.so),就添加该库的名称 TARGET_LINK_LIBRARIES(${PROJECT_NAME} m)编译阶段make 命令默认执行的是 make all,make all 有四个阶段: prepare test compile package make prepare : 只会执行prepare阶段,这个阶段主要是下载编译依赖 make test : 只会执行test阶段,这个阶段主要是做单元测试 make compile : 只会执行compile阶段,这个阶段主要是进行编译 make package : 只会执行packege阶段,这个阶段会把编译产出都copy到output目录中 make install : 只会执行install阶段,这个阶段是把编译产出放到$GOPATH/bin目录下,这个不是必须的 make clean : 只会执行clean阶段,这个阶段把编译输出的一些文件删除,恢复到编译之前的状态基本实践创建项目文件夹hello文件夹hello内,创建构建目录build文件夹hello内,创建源代码(main.cpp)文件夹hello内,创建CMakeLists.txt ...

October 2, 2019 · 1 min · jiezi

iOS编译自动升级版本号脚本

版权申明:本文原创首发于以下网站,您可以自由转载,但必须加入完整的版权声明博客园:https://www.cnblogs.com/Mogoo... csdn博客:https://blog.csdn.net/nmjkl001/ 知乎:https://www.zhihu.com/people/... 简书:https://www.jianshu.com/u/954... segmentfault:https://segmentfault.com/u/mo...使用方法Shell代码设置到xcode运行脚本里在每次成功构建后版本号会改变Shell代码#!/usr/bin/env bashecho $CONFIGURATIONif [ $CONFIGURATION == Release ]; thenecho " Bumping build number... "#!/bin/bashbuildNumber=$(/usr/libexec/PlistBuddy -c "Print CFBundleVersion" "$INFOPLIST_FILE")if [ "${buildNumber}" == "" ]; thenecho 2fivar1=${buildNumber##*.}var1=$(($var1 + 1))var2=${buildNumber%.*}buildNumber=${var2}.${var1} /usr/libexec/PlistBuddy -c "Set CFBundleVersion $buildNumber" "$INFOPLIST_FILE"echo " Bumped build number to $buildNumber " elseecho $CONFIGURATION " build - Not bumping build number. "fi我的联系方式:QQ:2161044579 邮箱:mogoostudio@outlook.com Github:https://github.com/MogooStudio

October 1, 2019 · 1 min · jiezi

compoer基本操作详解

原文转自微信公众号: qq1005349393 Composer介绍Composer 是 PHP 的一个<font color='red'>包依赖</font>管理工具。我们可以在项目中声明所依赖的外部工具库,Composer 会帮你安装这些依赖的库文件,有了它,我们就可以很轻松的使用一个命令将其他人的优秀代码引用到我们的项目中来。Composer 默认情况下不是全局安装,而是基于指定的项目的某个目录中(例如 vendor)进行安装。Composer 需要 PHP 5.3.2+ 以上版本,且需要开启 openssl。Composer 可运行在 Windows 、 Linux 以及 OSX 平台上。 Composer安装1.Windows安装Wondows 平台上,我们只需要下载 Composer-Setup.exe 后,一步步安装即可。需要注意的是你需要开启 openssl 配置,我们打开 php 目录下的 php.ini,将 extension=php_openssl.dll 前面的分号去掉就可以了。安装完成之后,检测是否安装成功。可以使用 composer --version命令查看,如下图:2.Linux安装 // 下载composer文件php -r "copy('https://install.phpcomposer.com/installer', 'composer-setup.php');"// 使用PHP解释器安装composerphp composer-setup.php// 移动到系统可执行文件目录,方便我们后期直接使用composer命令进行全局调用 mv composer.phar /usr/local/bin/composer3.Mac Os安装 ------直接安装// 下载并安装curl -sS https://getcomposer.org/installer | php// 移动到可执行文件目录,便于全局调用sudo mv composer.phar /usr/local/bin/composer------使用Mac上面的brew包管理工具安装brew install composer// 检测是否安装成功composer --version4.如何切换composer镜像源现在阿里处理自己的composer镜像源,并且能够做到与Packagist官网实时同步,推荐使用阿里的composer镜像源. // 切换镜像源composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/// 取消配置composer config -g --unset repos.packagist// 只针对当前项目切换镜像源(非全局切换)composer config repo.packagist composer https://mirrors.aliyun.com/composer/// 取消当前项目的镜像源composer config --unset repos.packagist5.composer更新composer的更新可以使用自身的命令来更新 ...

September 8, 2019 · 1 min · jiezi

Webpack-技巧-联合-alias-和-mainFields-提高多库联调效率

1、多库联调场景目前在开发一个工程项目,考虑到可扩展性和功能解耦,将每个功能模块都单独拆分出来。在正式使用、单独维护某个功能包的时候没什么问题,最为头疼的是联调两个功能模块的时候,就比较掣肘了。 我们举下面的场景为例来说明: 工程项目中有 A、B、C 这 3 个功能模块,每个功能模块都是单独的一个 npm 包;其中 A 是基础工具包,B、C 是业务功能模块;C 包依赖 A 包的功能工程目录及引用关系如下图所示: 1.1、联调的难题当我们开发 C 模块的时候,需要 同时联调 A 模块的功能,而由于 A 模块是以 npm 包放在在 node_modules 中,所以我们直接修改 A 模块并没有什么用,修改的效果并不会出现的 C 模块的调试内容中。 1.2、通常的解决方案通常我们的解决方法是采用 npm link 方式解决:先在 A 模块下执行 npm link;然后去 C 模块中执行 npm link A,这样我们就能方便联调这两个模块。 然而这种 npm link 的方式在某些场景还是有限制: 如果你使用 Typescript 开发,npm link 后有可能会报错找不到 A 模块中的 xx 类型定义;这个目前我也没有找到好的解决方案。。。如果 A 包只以压缩版本发行(即 dist 目录只存 index.min.js 文件),那么你每次修改 A 文件后必须手动打包一次,那么 C 模块才会感知文件的变化触发 hot reload如果没有上述这两个问题,我以往我都直接使用 npm link 的方式联合开发,联调地也是蛮开心的。 ...

July 7, 2019 · 1 min · jiezi

前端构建工具FIS3

https://fis.baidu.com/fis3/in... FIS3 是面向前端的工程构建工具。解决前端工程中性能优化、资源加载(异步、同步、按需、预加载、依赖管理、合并、内嵌)、模块化开发、自动化工具、开发规范、代码部署等问题。

June 5, 2019 · 1 min · jiezi

探索webpack运行时

前言本篇文章建议亲自动手尝试. 最近研究了 webpack 运行时源码, 在这篇文章中记录了我的探索 webpack 这个复杂的玩具方式, 并且以图形的形式将 webpack 运行时的流程给记录的下来. 我们讨论的是什么这篇文章主要记录的是 webpack 在将文件打包后是如何在浏览器中加载以及解析的流程. 手段webpack 实在是太复杂的了, 为了避免额外的干扰, 我使用最小化实例的方式来进行探究 webpack 是如何加载和解析模块文件的. 具体的手段:首先准备几个非常简单的 js 文件, 其次准备其对应的 webpack 配置文件, 逐一测试后观察其输出, 阅读其源码, 然后得出结论. 简单总结webpack 的运行时在解析同步模块的时候就和 nodejs 规则如出一辙. 什么意思, 每一个模块在运行前都会被一个函数进行包裹: (function (module, exports, __webpack_require__) { // do something})看起来和 nodejs 一样, 作用起来也一致, 这里的当前模块的导出模块和导入模块就是: exports_webpack_require_而当一个模块被需要的时候, webpack 就会执行这个函数获取其对应的 exports对象. 注意:我们需要的是模块执行完成后的 exports 对象而不是这个模块函数. 在 webpack 中我们可以使用 ESM 规范和 commonjs 规范来加载模块, 无论使用哪种规范, 实际上都可以被这种方式来包裹起来, 因为这两种常见的方式中都存在着相同的导入导出的概念, 所以 webpack 将这两种形式进行了统一包装消除了差异. ...

May 21, 2019 · 3 min · jiezi

webpack学习记录之可能面试思考题

本文记录一些webpack的知识点及面试过程中可能会出现的面试题或思考题?source map 基本使用与其原理是什么entry,output中常用的配置项有哪些,如何给静态资源配置CDN上环境external的使用tree shaking

April 20, 2019 · 1 min · jiezi

使用 Webpack 与 Babel 配置 ES6 开发环境

使用 Webpack 与 Babel 配置 ES6 开发环境安装 Webpack安装:# 本地安装$ npm install –save-dev webpack webpack-cli# 全局安装$ npm install -g webpack webpack-cli在项目根目录下新建一个配置文件—— webpack.config.js 文件:const path = require(‘path’);module.exports = { mode: ’none’, entry: ‘./src/index.js’, output: { filename: ‘bundle.js’, path: path.resolve(__dirname, ‘dist’) }}在 src 目录下新建 a.js 文件:export const isNull = val => val === nullexport const unique = arr => […new Set(arr)]在 src 目录下新建 index.js 文件:import { isNull, unique } from ‘./a.js’const arr = [1, 1, 2, 3]console.log(unique(arr))console.log(isNull(arr))执行编译打包命令,完成后打开 bundle.js 文件发现 isNull 和 unique 两个函数没有被编译,和 webpack 官方说法一致:webpack 默认支持 ES6 模块语法,要编译 ES6 代码依然需要 babel 编译器。安装配置 Babel 编译器使用 Babel 必须先安装 @babel/core 和 @babel/preset-env 两个模块,其中 @babel/core 是 Babel 的核心存在,Babel 的核心 api 都在这个模块里面,比如:transform。而 @babel/preset-env 是一个智能预设,允许您使用最新的 JavaScript,而无需微观管理您的目标环境需要哪些语法转换(以及可选的浏览器polyfill)。因为这里使用的打包工具是 Webpack,所以还需要安装 babel-loader 插件。安装:$ npm install –save-dev @babel/core @babel/preset-env babel-loader新建 .babelrc 文件:{ “presets”: [ “@babel/preset-env” ]}修改 webpack 配置文件(webpack.config.js):const path = require(‘path’);module.exports = { mode: ’none’, entry: ‘./src/index.js’, output: { filename: ‘bundle.js’, path: path.resolve(__dirname, ‘dist’) }, module: { rules: [ { test: /.js$/, loader: ‘babel-loader’, exclude: /node_modules/ } ] }}由于 babel 默认只转换 ES6 新语法,不转换新的 API,如:Set、Map、Promise等,所以需要安装 @babel/polyfill 转换新 API。安装 @babel/plugin-transform-runtime 优化代码,@babel/plugin-transform-runtime 是一个可以重复使用 Babel 注入的帮助程序代码来节省代码的插件。安装 @babel/polyfill、@babel/plugin-transform-runtime 两个插件:$ npm install –save-dev @babel/polyfill @babel/plugin-transform-runtime修改 .babelrc 配置文件:{ “presets”: [ ["@babel/preset-env", { “useBuiltIns”: “usage”, // 在每个文件中使用polyfill时,为polyfill添加特定导入。利用捆绑器只加载一次相同的polyfill。 “modules”: false // 启用将ES6模块语法转换为其他模块类型,设置为false不会转换模块。 }] ], “plugins”: [ ["@babel/plugin-transform-runtime", { “helpers”: false }] ]}最后,配置兼容的浏览器环境。在 .babelrc 配置文件中设置 targets 属性:{ “presets”: [ ["@babel/preset-env", { “useBuiltIns”: “usage”, “modules”: false, “targets”: { “browsers”: “last 2 versions, not ie <= 9” } }] ], “plugins”: [ ["@babel/plugin-transform-runtime", { “helpers”: false }] ]}执行命令编译代码,完成后检查 bundle.js 文件,是否成功转换新 API 。如果发现以下代码即说明转换成功:// 23.2 Set Objectsmodule.exports = webpack_require(80)(SET, function (get) { return function Set() { return get(this, arguments.length > 0 ? arguments[0] : undefined); };}, { // 23.2.3.1 Set.prototype.add(value) add: function add(value) { return strong.def(validate(this, SET), value = value === 0 ? 0 : value, value); }}, strong);其他关于 js 压缩和 Webpack 启用 tree shaking 功能的设置本文不在赘述。配置文件详情概览package.json 文件:{ “name”: “demo”, “version”: “1.0.0”, “description”: “”, “main”: “index.js”, “scripts”: { “test”: “echo "Error: no test specified" && exit 1”, “dev”: “webpack” }, “keywords”: [], “author”: “”, “license”: “ISC”, “devDependencies”: { “@babel/core”: “^7.3.4”, “@babel/plugin-transform-runtime”: “^7.3.4”, “@babel/polyfill”: “^7.2.5”, “@babel/preset-env”: “^7.3.4”, “babel-loader”: “^8.0.5”, “webpack”: “^4.29.6”, “webpack-cli”: “^3.2.3” }}webpack.config.js 文件:const path = require(‘path’);module.exports = { mode: ’none’, entry: ‘./src/index.js’, output: { filename: ‘bundle.js’, path: path.resolve(__dirname, ‘dist’) }, module: { rules: [ { test: /.js$/, loader: ‘babel-loader’, exclude: /node_modules/ } ] }}.babelrc 文件:{ “presets”: [ ["@babel/preset-env", { “useBuiltIns”: “usage”, “modules”: false, “targets”: { “browsers”: “last 2 versions, not ie <= 9” } }] ], “plugins”: [ ["@babel/plugin-transform-runtime", { “helpers”: false }] ]}符录usuallyjs 项目是本人最近建设的开源项目,欢迎感兴趣的同行交流。usuallyjs: https://github.com/JofunLiang/usuallyjs ...

March 11, 2019 · 2 min · jiezi

构建工具之目录

gulp总结

January 10, 2019 · 1 min · jiezi

构建多页面应用——hash

这里的hash主要从两个方面来说。一个是webpack生成的hash,另一个是页面链接中的hash,如:http://localhost:8080/ywbk.html#restaurant中的#restaurant。后者在单页面应用的路由处理中经常用到。webpack中的hashwebapck每次构建都会生成一个新的hash(主要用于生产环境)。它的作用就是用来标记构建生成的状态,通常使用的过程中,会将它注入到构建输出(生成)的文件名中。在webpack.config.js文件中的位置如下:…output: { path: path.resolve(__dirname, ‘dist/’), filename: isDev ? ‘assets/js/[name].js’ : ‘assets/js/[name].[contenthash].js’, publicPath: isDev ? config.devUrl : config.deployUrl},module: { rules: [ … { include: path.resolve(__dirname, ‘assets/imgs/other/’), test: /.(png|jpe?g|gif)$/, use: [ { loader: ‘file-loader’, options: { name: isDev ? ‘[name].[ext]’ : ‘[name].[hash].[ext]’, outputPath: ‘assets/imgs/’ } } ] } … ]}…plugins: plugins.concat([ new MiniCssExtractPlugin({ filename: isDev ? ‘assets/css/[name].css’ : ‘assets/css/[name].[contenthash].css’, chunkFilename: isDev ? ‘assets/css/[id].css’ : ‘assets/css/[id].[contenthash].css’ }) …])如果项目是第一次构建,那么所有的静态资源都会被添加同一个hash,当第二构建如果有内容改变,构建生成的文件会被赋予新的hash。为了提高见构建的速度,减少构建生成不必要的文件(如果第一次生成的文件和第二次生成的文件内容相同,那么没必要重新在生成一次)。为此webpack提供了三个hash字段可供选择,分别是hash、chunkhash、contenthash。hash:不管内容改变与否,所有的文件都会被重新生成一遍。这是一种粗放的模式。chunkhash:它主要针对与webpack配置文件的entry中定义的入口文件。contenthash: 它主要针对的是webpack构建的过程中,提取(或者说分离)出来的内容,如:extract-text-webpack-plugin针对多页面应用的构建特点,使用contenthash是一个不错的选择,本文中所使用的示例wepbakck4.x-multi-page符合这个特点。css的分离,js文件的分离(webpack4的SplitChunk)。为什么要在多页面应用中使用单页面应用的hash来实现路由的控制演示效果很明显的一点是传统的多页面应用的业务模块往往会出现多个页面之间会有很多相同内容,这样在单击导航实现路由切换的时候,总是会看到相同的内容,这样会给用户造成一种错觉‘为什么总是同一个页面’。这样的用户体验往往不好。最突出的就是包含二级导航的页面。(可参考圣捷集团的官网)单页面应用给我们了一个很好的启示,可以通过将这些页面结构相似的,而只有一部分内容类同的页面组合成为一个页面。这样做的好处显而易见,减少多页面构建生成的页面数量,我们之构建生成一级和二级页面,以及一些页面结构很少雷同的页面,而不构建生成三级页面。优化后的示例地址具体的实现有两种解决方案。第一种,在每个页面中使用一个vue(结合vue-router)的示例(也可以使用,react,angular)。第二种,自己实现对不同hash的处理。注:如果使用hash,在开发的时候一定要模拟一个服务器环境,直接用浏览器打开是无法实现的,浏览器控制台会提示跨域的错误。本文所使用的示例用的是第二种方案。具体的实现过程如下:在生成子导航的模拟数据中添加了一个type值,tabs: [ { cn_name: ‘圣捷投资’, en_name: ‘SHENGJIE INVESTMENT’, type: ‘investment’ }, { cn_name: ‘董事长致辞’, en_name: ‘CHAIRMANS SPEECH’, type: ‘speech’ } …]这样使用pug-loader处理生成的html对应的元素中会包含一个data-type自定义属性。参考代码如下:<div class=“tabs”> <div class=“tab-item active” data-type=“finance”> <div>互联网金融</div> <div>ONLINE FINANCE</div> </div> <div class=“tab-item” data-type=“allfinance”> <div>全品类金融</div> <div>WHOLE CATEGORY FINANCE</div> </div> …</div>然后,使用JavaScript通过控制触发条件,如url的hash改变,进而控制页面的展示效果。参考代码如下:···$(’.tab-item’).on(‘click’, function() { var type = $(this).data(’type’) window.location.hash = type tab(this, type)})···首先,单点击二级导航时,改变url的hash。这样做可以让用户通过操作浏览器的前进和后退按钮来控制页面,此外使用浏览器的前进和后退按钮的好处是,浏览可以记录页面的状态。(只用上面的代码无法实现想要的效果)其次,使用hashchange这个浏览器自带的监听hash改变的api(他兼容>=ie8的浏览器,所以可以放心使用)。···$(window).on(‘hashchange’, function() { tabcheck()})···通过它们,就可以轻松的实现url的hash。为了页面呈现更好的效果,可以给页面添加一个滚动的动画,如果不使用hash在传统的页面中实现有些棘手。那么针对页面底部的网站导航,如何结合hash来操作页面并实现一致的路由切换效果呢?这里需要监听页面的load状态,在webpack中,使用commonjs来组织js代码块,需要注意window.load(…)无效的情况。具体的实现就不一一介绍了,可参考demo中tabs.js文件的代码源代码webpack4.x multi-page构建多页面应用系列文章构建多页面应用构建多页面应用——单个页面的处理构建多页面应用——模板构建多页面应用——静态资源构建多页面应用——优化(一) ...

January 6, 2019 · 1 min · jiezi

构建多页面应用——优化(一)

构建多页面应用的过程中,需要优化代码的结构。而优化代码的结构,往往会使用函数化编程。可参考webpack4.x-demo。在进行多页面应用编程的过程中,webpack.config.js的代码规模会随着项目规模的增加而增加。而造成webpack.config.js文件增大的原因,主要是从entry,plugins的配置,而造成plugins增加是html-webpack-plugin和webpack-spritesmith的使用。因为每增加一个页面,就需要增加一个entry和实例化的html-wepback-plguin,而每增加一个雪碧图就要使用一个webpack-spritesimth。为了更好的展示多页面应用的开发,我将近期优化的一个项目的部分代码整理了一下。演示地址,源代码参考文章结尾提供的地址。项目结构的改动为了方便发布,需要调整一下项目的目录结构。需要把assets文件夹提取到根目录下,这个文件包含了项目所有的资源文件,如果放在src目录下,会造成路径问题,如:图片、字体。如果不调整,会造成在开发的模式下可以访问到的路径,而在生产模式下不能访问。所以,为了保证开发模式和生产模式下静态资源的路径一致,需要这样的调整。注:如果用vue做过开发的话,相信你就会明白,使用vue-cli生成的项目,在根目录下有一个static文件夹,它用来保存项目开发的过程中引用到的所有的静态资源,而本文中介绍的示例中的assets文件夹有类似的作用,这个文件夹后续还会继续优化。entry首先,为了简化开发中的重复定义,添加了pages/utils/nav.js和pages/utils/subnav.js文件。它们用来存放项目各页面的路由和seo优化所需要的信息。代表每个页面的结构如下:[ { href: ‘/’, text: ‘首页’, name: ‘index’, meta: { ‘description’: ‘这是首页’, ‘keywords’: ‘webpack, multi-page, 首页’, ‘author’: ‘https://github.com/lvzhenbang/’ } …]然后,根据需要新添加了一个multipage.config.js文件,在这里定义了如何生成一个entry,代码如下:const path = require(‘path’)let navs = require(’./pages/utils/nav’)const subnavs = require(’./pages/utils/subnav’)navs = navs.concat(subnavs)// entrylet entry = {}for (let nav of navs) { entry[nav.name] = ‘./pages/’ + nav.name + ‘.js’}// commons css/jsentry.other = [’./pages/utils/commons.js’, ‘./pages/utils/css.js’]pluginsplugins 包含两部分。第一部分,html-wepback-plugin,修改multipage.config 代码如下:···// make pagesconst HtmlWebapckPlugin = require(‘html-webpack-plugin’);let plugins = []for (let nav of navs) { plugins.push(new HtmlWebapckPlugin({ /* inital page / filename: nav.name + ‘.html’, chunks: [nav.name, ‘other’], / page head / title: nav.text, meta: nav.meta, favicon: path.resolve(__dirname, ‘assets/favicon.jpg’), template: path.resolve(__dirname, ‘pages/’ + nav.name + ‘.pug’), minify: true }))}第二部分,webpack-spritesimth生成雪碧图,修改multipage.config.js,代码如下:…// sprites const SpritesmithPlugin = require(‘webpack-spritesmith’);const sprites = require(’./pages/utils/sprites’)for(let sprite of sprites) { plugins.push(new SpritesmithPlugin({ src: { cwd: path.resolve(__dirname, ‘assets/imgs/sprites/’ + sprite + ‘/’), glob: ‘.png’ }, target: { image: path.resolve(__dirname, ‘assets/imgs/other/’ + sprite + ‘-sprite.png’), css: path.resolve(__dirname, ‘assets/css/’ + sprite + ‘/’ + sprite + ‘-sprite.scss’) }, apiOptions: { cssImageRef: ‘../../imgs/other/’ + sprite + ‘-sprite.png’ } }))}根据项目需要,需添加pages/utils/sprites.js文件,它用来保存要合成的雪碧图的名字,这个名字也是合成雪碧图所需的图片所在的文件夹名字。这个文件比较简单,用来导出一个字符串数组。注:至于为什么只用一个字符串数组,构建多页面应用——静态资源这篇文章可以说明。引用multipage.config.js在webapck.config.js文件中引用multipage.config.js,代码如下:…const multipage = require(’./multipage.config’)let entry = multipage.entry, plugins = multipage.pluginsmodule.exports = (mode) => { … return { entry: entry, … plugins: plugins.concat([ new MiniCssExtractPlugin({ filename: isDev ? ‘assets/css/[name].css’ : ‘assets/css/[name].[contenthash].css’, chunkFilename: isDev ? ‘assets/css/[id].css’ : ‘assets/css/[id].[contenthash].css’ }), new CopyWebpackPlugin([ { from: path.resolve(__dirname, ‘assets/imgs/other/’), to: path.resolve(__dirname, ‘dist/assets/imgs/other/’), ignore: [’.*’] } ]) ]) }}当然,你可以考虑使用webpack-merge。源代码webpack4.x multi-page构建多页面应用系列文章构建多页面应用构建多页面应用——单个页面的处理构建多页面应用——模板构建多页面应用——静态资源 ...

January 5, 2019 · 1 min · jiezi

构建多页面应用——静态资源的处理

在之前的系列文章中,我已经介绍了如何用webpack实现多页面应用的js,html,css的处理。今天就主要介绍如何处理静态资源,在web开发中最常见的静态资源就是图片。图片的引用方式而因为在web中,图片有两种主要的引入方式,第一种是<img src="…">,第二种是backgorund-image: url(…)。前者在html中使用,后者在css中使用。web开发中的图片处理图片常见的类型有jp(e)g,png,gif,包括现在普遍使用的svg以及webp。svg作为矢量图形,有一定的使用场景,而webp作为未来web开发的趋势,有一定的使用场景,比如:爱奇艺中的轮播图(carousel)中就是用webp,但其他的见到的不多。现在,web开发中使用最多的还是jpg和png,处理他们,在构建工具中使用url-loader和file-loader就好了,其中file-loader负责图片的拷贝和输出,并会给图片名添加一个hash值。说到这里,很多人会想到字体图标。以前处理web页面中的图标(icon),使用图片来处理,这样会带来一个性能问题就是http请求的增多,这样会造成服务器的负载压力,同时会带来用户体验的问题,因为会出现页面的局部空白和页面重绘的问题,当然一种解决方案使雪碧图(sprite),但是如果图片过大怎么解决,如何对图片进行分解(大变小的问题),图片的拼接比较困难,最要命的是在引用雪碧图时要进行计算,除此之外就是如何对雪碧图的组成图片进行自定义的删减,而使用字体图标这些问题,都会得到一定程度的解决,当然在构建工具中可以使用webpack-spritesmith这个插件来处理组成雪碧图的图片。当然,有些特殊的情况,需要使用base64,这里使用url-loader即可。将图片处理为base64有使用场景,将图片转换为雪碧图亦有使用场景,单独的图片处理也有使用场景(这些使用场景的图片大小从左到右依次增大)。这些场景在一个web项目中都会涉及到。虽然使用字体图标可以替代雪碧图,因为字体图标有更小的尺寸,更自由的操作手法(如:图标颜色的自定义),但是一个DIY的web项目还是有些图标还是需要雪碧图。但是,这里有一个问题,如何在一个项目中同时使用base64,雪碧图,字体图标,单独的图片。在构建中如何使用多种图片处理方式在构建多页面应用中,如何解决呢?字体图标处理字体图标很简单,如:iconfont(阿里巴巴字体图标库),就像引用css那么简单。base64base64的处理,使用url-loader。雪碧图雪碧图的处理,可使用webpack-spritesmith这个插件单独的图片使用file-loader,它负责拷贝url-loader的处理结果,并输出。上面就是我们常见的图片处理,如果要处理svg可以参考svg-url-loader,如果要处理webp可以参考webp-loader如何对图片进行优化对图片进行优化,会带来良好的用户体验。熟悉图片优化的都知道渐进式(progressive),可参考nuwen.netjp(e)g可以进行连续性处理,这样可保证图片数据请求回来多少,就渲染多少,是自上而下的渲染,也是有模糊到清晰的状态。png可以进行交叉处理,这样也可保证图片数据请求回来多少,就渲染多少,它是整体的显示,而且是又模糊状态到清晰的状态。gif图片一般使用小图,如果是大图会记号浏览器性能,还不如使用视频,或者用css动画来代替。我个人整理了一个css 动画集,有需要的可以看一下。在构建多页面应用中,会使用到image-webpack-loader来做优化处理。其中,配置项options中的mozjpeg 处理jp(e)g图片,pngquant处理png图片,gifsicle处理gif图片,webp处理webp图片。多页面应用中的图片处理首先,看一下多页面应用中的目录结构图:./src│ aboutUs.js│ contactUs.js│ css.js│ index.js│ recruitment.js│ ├─assets│ │ favicon.jpg│ │ │ ├─css│ │ │ index.scss│ │ │ │ │ ├─commons│ │ │ ├─container│ │ │ │ index.scss│ │ │ │ │ │ │ ├─footer│ │ │ │ index.scss│ │ │ │ │ │ │ └─header│ │ │ index.scss│ │ │ │ │ ├─productus│ │ │ index.scss│ │ │ productus-sprite.scss│ │ │ │ │ └─utils│ │ btn.scss│ │ form.scss│ │ inital.scss│ │ list.scss│ │ modeal.scss│ │ normalize.scss│ │ pagination.scss│ │ popover.scss│ │ table.scss│ │ text.scss│ │ tooltip.scss│ │ │ └─imgs│ ├─base64│ │ fe.jpg│ │ │ ├─other│ │ float.jpg│ │ productus-sprite.png│ │ │ └─sprites│ └─productus│ product-us_01.png│ product-us_02.png│ product-us_03.png│ product-us_04.png│ product-us_05.png│ product-us_06.png│ product-us_07.png│ product-us_08.png│ product-us_09.png│ product-us_10.png│ product-us_11.png│ product-us_12.png│ ├─pages│ │ recruitment.pug│ │ template.pug│ │ │ └─components│ ├─commons│ │ ├─container│ │ │ index.pug│ │ │ │ │ ├─footer│ │ │ index.pug│ │ │ │ │ └─header│ │ index.pug│ │ │ └─productus│ index.pug│ └─utils load.js跟以前的实例代码相比,这次的文件目录结构变化较大,这里将要处理的所有文件模块都放在了src目录下。可能有人会问,为什么要要将目录分的这么细,下面我就说一下为什么这么分。aboutUs.js, contactUs.js, index.js, recruitment.js是四个路由页面,要用到的js代码,css.js处理各个路由页面公用的css代码;静态资源目录(assets)下,存放web项目常用的静态资源;静态资源目录下的css目录统一存放整个web项目所用到的css样式。其中commons存放公用的css模块,每个公用模块有创建一个目录存放该公用模块可以使用到的css模块(提醒,不要分的过于细),而其他的如productus存放产品模块代码,根据开发的需要可以创建其他的模块目录,目录结构类似于commons中的header模块,其中utils放置自己总结的工具模块代码,如table,form等。然后,指定style-loader,css-loader等样式相关的loader处理css样式文件,这样可以减少遍历,缩短构建时间。静态资源目录下的imgs目录,用来存放整个项目中,用到的图片。在这里,分为base64,sprite,ohter等,为什么要这样分?如果分的话file-loader这个webapck的loader会复制并导出imgs目下所有的图片,者在构建中并不是我们需要的,这样会增加构建的时间。如果让url-loader处理base64目录下的图片,file-loader处理ohter目录下的图片,webpack-spritesmith处理sprite目录下的图片,并将生成的图片放到ohter目录下,用file-loader进行二次处理。这样做,webpack处理更精确,可以减少不必要的遍历,极大地减少构建的时间,同样方便对图片的管理,特别是对于需要改变sprite的图片的管理。在imgs目录下创建base64目录,sprite目录,使用file-loader指定处理ohter目录,是为了避免file-loader将所有的图片都拷贝一份并导出到dist输出目录中,因为与base64相关的文件已经在css样式文件中了,再拷贝一份,已经没有意义,而sprite相关的文件会被webpack-spritesmith插件先处理生成一个文件,所以再拷贝它们也没有意义,还会让构建速度更慢。在page目录下,放置所有的html代码块(这里使用pug编译器生成相应的html代码块),它的目录分类和css相类似,它们是一一对应的关系。注意:iamge-webpack-loader,要先对所有的图片进行优化处理,然后再用其他loader处理。loader的执行顺序,如果你是style-loader!css-loader!sass-loader"使用,它是从右到左方向先后执行,如果你是在配置文件中的rules: […]数组中,它也是从右到左的方向执行,如果你将所有的loader规则有回车符号隔开,那么它就是自下而上的执行。一类特殊的图片引用针对<img src="…">的图片使用,ul-loader是不会处理html中的img引用,现在处理这样情况的loader或插件,也并没有一个比较出名的。现在通用的做法就是将图片拷贝一份到生成目录中,copy-webpack-plugin。参考配置代码如下:new CopyWebpackPlugin([ { from: path.resolve(__dirname, ‘assets/imgs/other/’), to: path.resolve(__dirname, ‘dist/assets/imgs/other/’), ignore: [’.’] }]),但这样,会带来另一个问题就是图片的优化问题,如何使用 image-webpack-loader?本来的目的是对项目中使用到的所有的图片进行优化,而现在只能对base64和sprite目录下的图片进行优化处理。不过,不要慌,可以通过创建一个新的npm脚本命令(本是里使用的是npm run img)来对图片进行压缩处理,新建了一个目录static用来保存优化前的图片,ohter用来保存优化后的图片。首先,需要安装imagemin, imagemin-mozjpeg, imagemin-optipng, imagemin-gifsicle :yarn add imagemin imagemin-mozjpeg imagemin-optipng imagemin-gifsicle –dev然后,在项目的根目录添加一个优化图片的文件optzing-img.js,代码如下:const path = require(‘path’)const imagemin = require(‘imagemin’);const imageminMozjpeg = require(‘imagemin-mozjpeg’);const imageminOptipng = require(‘imagemin-optipng’);const imageminGifsicle = require(‘imagemin-gifsicle’);(async () => { await imagemin( [ path.resolve(__dirname, ‘src/assets/imgs/static/.jpg’), path.resolve(__dirname, ‘src/assets/imgs/static/.png’), path.resolve(__dirname, ‘src/assets/imgs/static/.gif’) ], path.resolve(__dirname, ‘src/assets/imgs/other/’), { use: [ imageminMozjpeg(), imageminOptipng(), imageminGifsicle() ] } ); console.log(‘图片优化完成!’);})();最后,在package.json文件中添加如下的npm命令:…“scripts”: { … “img”: “node optzing-img.js” },…在控制输入npm run img,然后按下回车键就可以得到你所需要的。注:既然重新定义了图片优化的npm脚本命令,那么,是否需要去掉之前在webpack.config.js中的image-webpakc-loader,当然不需要。主要有两个原因,一个是sprite雪碧图它是用几张小图片合成了一张大图片,这张合成的图片还需要优化;另一个是因为本项目对于存放图片的目录进行了细化。字体在web开发中,自定义的字体也是比较常见的,在webpack中它的处理和图片类似,都是使用的 url-loader 和 file-loader。参考代码如下:…{ include: path.resolve(__dirname, ‘assets/fonts/’), test: /.(woff2?|eot|ttf|otf)(?.*)?$/, use: [{ loader: ‘url-loader’, options: { limit: 10000, name: isDev ? ‘[name].[ext]’ : ‘[name].[hash].[ext]’, outputPath: ‘assets/fonts/’ } }]},…新创建了一个fonts目录又来存放项目开发过程中使用的字体。源代码webpack4.x multi-page此后,webpack构建多页面应用系列文章的源代码,都在这个github项目中,webpack3.x multi-page不再维护。构建多页面应用系列文章构建多页面应用构建多页面应用——单个页面的处理构建多页面应用——模板构建多页面应用——静态资源 ...

January 1, 2019 · 2 min · jiezi

webpack-chain项目中文翻译

webpack-chain注意:这是对原项目readme文件的翻译,为啥翻译这个呢,因为Vue CLI3脚手架生成的项目使用这种方式配置webpack,但是脚手架中对这块的介绍不多,所以把这部分翻译出来,以供团队和大家参考。应用一个链式 API 来生成和简化 2-4 版本的webpack的配置的修改。此文档对应于webpack-chain的v5版本,对于以前的版本,请参阅:v4 docsv3 docsv2 docsv1 docs注意: 虽然 webpack-chain 被广泛应用在Neutrino中,然而本软件包完全独立,可供任何项目使用。介绍webpack 的核心配置的创建和修改基于一个有潜在难于处理的 JavaScript 对象。虽然这对于配置单个项目来说还是 OK 的,但当你尝试跨项目共享这些对象并使其进行后续的修改就会变的混乱不堪,因为您需要深入了解底层对象的结构以进行这些更改。webpack-chain 尝试通过提供可链式或顺流式的 API 创建和修改webpack 配置。API的 Key 部分可以由用户指定的名称引用,这有助于 跨项目修改配置方式 的标准化。通过以下示例可以更容易地解释这一点。安装webpack-chain 需要 Node.js v6.9及更高版本. webpack-chain 也只创建并被设计于使用webpack的2,3,4版本的配置对象。你可以使用Yarn或者npm来安装此软件包(俩个包管理工具选一个就行):Yarn方式yarn add –dev webpack-chainnpm方式npm install –save-dev webpack-chain入门当你安装了 webpack-chain, 你就可以开始创建一个webpack的配置。 对于本指南,我们的示例基本配置 webpack.config.js 将位于我们项目的根目录。// 导入 webpack-chain 模块,该模块导出了一个用于创建一个webpack配置API的单一构造函数。const Config = require(‘webpack-chain’);// 对该单一构造函数创建一个新的配置实例const config = new Config();// 用链式API改变配置// 每个API的调用都会跟踪对存储配置的更改。config // 修改 entry 配置 .entry(‘index’) .add(‘src/index.js’) .end() // 修改 output 配置 .output .path(‘dist’) .filename(’[name].bundle.js’);// 创建一个具名规则,以后用来修改规则config.module .rule(’lint’) .test(/.js$/) .pre() .include .add(‘src’) .end() // 还可以创建具名use (loaders) .use(’eslint’) .loader(’eslint-loader’) .options({ rules: { semi: ‘off’ } });config.module .rule(‘compile’) .test(/.js$/) .include .add(‘src’) .add(’test’) .end() .use(‘babel’) .loader(‘babel-loader’) .options({ presets: [ [’@babel/preset-env’, { modules: false }] ] });// 也可以创建一个具名的插件!config .plugin(‘clean’) .use(CleanPlugin, [[‘dist’], { root: ‘/dir’ }]);// 导出这个修改完成的要被webpack使用的配置对象module.exports = config.toConfig();共享配置也很简单。仅仅导出配置 和 在传递给webpack之前调用 .toConfig() 方法将配置导出给webpack使用。// webpack.core.jsconst Config = require(‘webpack-chain’);const config = new Config();// 跨目标共享配置// Make configuration shared across targets// …module.exports = config;// webpack.dev.jsconst config = require(’./webpack.core’);// Dev-specific configuration// 开发具体配置// …module.exports = config.toConfig();// webpack.prod.jsconst config = require(’./webpack.core’);// Production-specific configuration// 生产具体配置// …module.exports = config.toConfig();ChainedMapwebpack-chain 中的核心API接口之一是 ChainedMap. 一个 ChainedMap的操作类似于JavaScript Map, 为链式和生成配置提供了一些便利。 如果一个属性被标记一个 ChainedMap, 则它将具有如下的API和方法:除非另有说明,否则这些方法将返回 ChainedMap , 允许链式调用这些方法。// 从 Map 移除所有 配置.clear()// 通过键值从 Map 移除单个配置.// key: *delete(key)// 获取 Map 中相应键的值// key: *// returns: valueget(key)// 获取 Map 中相应键的值// 如果键在Map中不存在,则ChainedMap中该键的值会被配置为fn的返回值.// key: *// fn: Function () -> value// returns: valuegetOrCompute(key, fn)// 配置Map中 已存在的键的值// key: *// value: *set(key, value)// Map中是否存在一个配置值的特定键,返回 真或假// key: *// returns: Booleanhas(key)// 返回 Map中已存储的所有值的数组// returns: Arrayvalues()// 返回Map中全部配置的一个对象, 其中 键是这个对象属性,值是相应键的值,// 如果Map是空,返回 undefined// 使用 .before() 或 .after() 的ChainedMap, 则将按照属性名进行排序。// returns: Object, undefined if emptyentries()// 提供一个对象,这个对象的属性和值将 映射进 Map。// 你也可以提供一个数组作为第二个参数以便忽略合并的属性名称。// obj: Object// omit: Optional Arraymerge(obj, omit)// 对当前配置上下文执行函数。// handler: Function -> ChainedMap // 一个把ChainedMap实例作为单个参数的函数batch(handler)// 条件执行一个函数去继续配置// condition: Boolean// whenTruthy: Function -> ChainedMap // 当条件为真,调用把ChainedMap实例作为单一参数传入的函数// whenFalsy: Optional Function -> ChainedMap // 当条件为假,调用把ChainedMap实例作为单一参数传入的函数when(condition, whenTruthy, whenFalsy)ChainedSetwebpack-chain 中的核心API接口另一个是 ChainedSet. 一个 ChainedSet的操作类似于JavaScript Map, 为链式和生成配置提供了一些便利。 如果一个属性被标记一个 ChainedSet, 则它将具有如下的API和方法:除非另有说明,否则这些方法将返回 ChainedSet , 允许链式调用这些方法。// 添加/追加 给Set末尾位置一个值.// value: *add(value)// 添加 给Set开始位置一个值.// value: prepend(value)// 移除Set中全部值.clear()// 移除Set中一个指定的值.// value: delete(value)// 检测Set中是否存在一个值.// value: // returns: Booleanhas(value)// 返回Set中值的数组.// returns: Arrayvalues()// 连接给定的数组到 Set 尾部。// arr: Arraymerge(arr)// 对当前配置上下文执行函数。// handler: Function -> ChainedSet // 一个把 ChainedSet 实例作为单个参数的函数batch(handler)// 条件执行一个函数去继续配置// condition: Boolean// whenTruthy: Function -> ChainedSet // 当条件为真,调用把 ChainedSet 实例作为单一参数传入的函数// whenFalsy: Optional Function -> ChainedSet // 当条件为假,调用把 ChainedSet 实例作为单一参数传入的函数when(condition, whenTruthy, whenFalsy)速记方法存在许多简写方法,用于 使用与简写方法名称相同的键在 ChainedMap 设置一个值例如, devServer.hot 是一个速记方法, 因此它可以用作:// 在 ChainedMap 上设置一个值的 速记方法devServer.hot(true);// 上述方法等效于:devServer.set(‘hot’, true);一个速记方法是可链式的,因此调用它将返回 原实例,允许你继续链式使用配置创建一个新的配置对象const Config = require(‘webpack-chain’);const config = new Config();移动到API的更深层将改变你正在修改的内容的上下文。 你可以通过 config在此引用顶级配置或者通过调用 .end() 方法向上移动一级 使你移回更高的 上下文环境。如果你熟悉jQuery, 这里与其 .end() 工作原理类似。除非另有说明,否则全部的API调用都将在当前上下文中返回API实例。 这样,你可以根据需要连续 链式API调用. 有关对所有速记和低级房费有效的特定值的详细信息,请参阅 webpack文档层次结构 中的相应名词。Config : ChainedMap配置速记方法config .amd(amd) .bail(bail) .cache(cache) .devtool(devtool) .context(context) .externals(externals) .loader(loader) .mode(mode) .parallelism(parallelism) .profile(profile) .recordsPath(recordsPath) .recordsInputPath(recordsInputPath) .recordsOutputPath(recordsOutputPath) .stats(stats) .target(target) .watch(watch) .watchOptions(watchOptions)配置 entryPoints// 回到 config.entryPoints : ChainedMapconfig.entry(name) : ChainedSetconfig .entry(name) .add(value) .add(value)config .entry(name) .clear()// 用低级别 config.entryPoints:config.entryPoints .get(name) .add(value) .add(value)config.entryPoints .get(name) .clear()配置 output: 速记 方法config.output : ChainedMapconfig.output .auxiliaryComment(auxiliaryComment) .chunkFilename(chunkFilename) .chunkLoadTimeout(chunkLoadTimeout) .crossOriginLoading(crossOriginLoading) .devtoolFallbackModuleFilenameTemplate(devtoolFallbackModuleFilenameTemplate) .devtoolLineToLine(devtoolLineToLine) .devtoolModuleFilenameTemplate(devtoolModuleFilenameTemplate) .filename(filename) .hashFunction(hashFunction) .hashDigest(hashDigest) .hashDigestLength(hashDigestLength) .hashSalt(hashSalt) .hotUpdateChunkFilename(hotUpdateChunkFilename) .hotUpdateFunction(hotUpdateFunction) .hotUpdateMainFilename(hotUpdateMainFilename) .jsonpFunction(jsonpFunction) .library(library) .libraryExport(libraryExport) .libraryTarget(libraryTarget) .path(path) .pathinfo(pathinfo) .publicPath(publicPath) .sourceMapFilename(sourceMapFilename) .sourcePrefix(sourcePrefix) .strictModuleExceptionHandling(strictModuleExceptionHandling) .umdNamedDefine(umdNamedDefine)配置 resolve(解析): 速记方法config.resolve : ChainedMapconfig.resolve .cachePredicate(cachePredicate) .cacheWithContext(cacheWithContext) .enforceExtension(enforceExtension) .enforceModuleExtension(enforceModuleExtension) .unsafeCache(unsafeCache) .symlinks(symlinks)配置 resolve 别名config.resolve.alias : ChainedMapconfig.resolve.alias .set(key, value) .set(key, value) .delete(key) .clear()配置 resolve modulesconfig.resolve.modules : ChainedSetconfig.resolve.modules .add(value) .prepend(value) .clear()配置 resolve aliasFieldsconfig.resolve.aliasFields : ChainedSetconfig.resolve.aliasFields .add(value) .prepend(value) .clear()配置 resolve descriptionFieldsconfig.resolve.descriptionFields : ChainedSetconfig.resolve.descriptionFields .add(value) .prepend(value) .clear()配置 resolve extensionsconfig.resolve.extensions : ChainedSetconfig.resolve.extensions .add(value) .prepend(value) .clear()配置 resolve mainFieldsconfig.resolve.mainFields : ChainedSetconfig.resolve.mainFields .add(value) .prepend(value) .clear()配置 resolve mainFilesconfig.resolve.mainFiles : ChainedSetconfig.resolve.mainFiles .add(value) .prepend(value) .clear()配置 resolveLoader当前API config.resolveLoader 相同于 配置 config.resolve 用下面的配置:配置 resolveLoader moduleExtensionsconfig.resolveLoader.moduleExtensions : ChainedSetconfig.resolveLoader.moduleExtensions .add(value) .prepend(value) .clear()配置 resolveLoader packageMainsconfig.resolveLoader.packageMains : ChainedSetconfig.resolveLoader.packageMains .add(value) .prepend(value) .clear()配置 performance(性能): 速记方法config.performance : ChainedMapconfig.performance .hints(hints) .maxEntrypointSize(maxEntrypointSize) .maxAssetSize(maxAssetSize) .assetFilter(assetFilter)配置 optimizations(优化): 速记方法config.optimization : ChainedMapconfig.optimization .concatenateModules(concatenateModules) .flagIncludedChunks(flagIncludedChunks) .mergeDuplicateChunks(mergeDuplicateChunks) .minimize(minimize) .namedChunks(namedChunks) .namedModules(namedModules) .nodeEnv(nodeEnv) .noEmitOnErrors(noEmitOnErrors) .occurrenceOrder(occurrenceOrder) .portableRecords(portableRecords) .providedExports(providedExports) .removeAvailableModules(removeAvailableModules) .removeEmptyChunks(removeEmptyChunks) .runtimeChunk(runtimeChunk) .sideEffects(sideEffects) .splitChunks(splitChunks) .usedExports(usedExports)配置 optimization minimizers(最小优化器)// 回到 config.optimization.minimizersconfig.optimization .minimizer(name) : ChainedMap配置 optimization minimizers: 添加注意: 不要用 new 去创建最小优化器插件,因为已经为你做好了。config.optimization .minimizer(name) .use(WebpackPlugin, args)// 例如config.optimization .minimizer(‘css’) .use(OptimizeCSSAssetsPlugin, [{ cssProcessorOptions: { safe: true } }])// Minimizer 插件也可以由它们的路径指定,从而允许在不使用插件或webpack配置的情况下跳过昂贵的 require s。config.optimization .minimizer(‘css’) .use(require.resolve(‘optimize-css-assets-webpack-plugin’), [{ cssProcessorOptions: { safe: true } }])配置 optimization minimizers: 修改参数config.optimization .minimizer(name) .tap(args => newArgs)// 例如config .minimizer(‘css’) .tap(args => […args, { cssProcessorOptions: { safe: false } }])配置 optimization minimizers: 修改实例config.optimization .minimizer(name) .init((Plugin, args) => new Plugin(…args));配置 optimization minimizers: 移除config.optimization.minimizers.delete(name)配置插件// 回到 config.pluginsconfig.plugin(name) : ChainedMap配置插件: 添加注意: 不要用 new 去创建插件,因为已经为你做好了。config .plugin(name) .use(WebpackPlugin, args)// 例如config .plugin(‘hot’) .use(webpack.HotModuleReplacementPlugin);// 插件也可以由它们的路径指定,从而允许在不使用插件或webpack配置的情况下跳过昂贵的 require s。config .plugin(’env’) .use(require.resolve(‘webpack/lib/EnvironmentPlugin’), [{ ‘VAR’: false }]);配置插件: 修改参数config .plugin(name) .tap(args => newArgs)// 例如config .plugin(’env’) .tap(args => […args, ‘SECRET_KEY’]);配置插件: 修改实例config .plugin(name) .init((Plugin, args) => new Plugin(…args));配置插件: 移除config.plugins.delete(name)配置插件: 在之前调用指定当前插件上下文应该在另一个指定插件之前执行,你不能在同一个插件上同时使用 .before() 和 .after()。config .plugin(name) .before(otherName)// 例如config .plugin(‘html-template’) .use(HtmlWebpackTemplate) .end() .plugin(‘script-ext’) .use(ScriptExtWebpackPlugin) .before(‘html-template’);Config plugins: 在之后调用指定当前插件上下文应该在另一个指定插件之后执行,你不能在同一个插件上同时使用 .before() 和 .after()。config .plugin(name) .after(otherName)// 例如config .plugin(‘html-template’) .after(‘script-ext’) .use(HtmlWebpackTemplate) .end() .plugin(‘script-ext’) .use(ScriptExtWebpackPlugin);配置 resolve 插件// 回到 config.resolve.pluginsconfig.resolve.plugin(name) : ChainedMap配置 resolve 插件: 添加注意: 不要用 new 去创建插件,因为已经为你做好了。config.resolve .plugin(name) .use(WebpackPlugin, args)配置 resolve 插件: 修改参数config.resolve .plugin(name) .tap(args => newArgs)配置 resolve 插件: 修改实例config.resolve .plugin(name) .init((Plugin, args) => new Plugin(…args))配置 resolve 插件: 移除config.resolve.plugins.delete(name)配置 resolve 插件: 在之前调用指定当前插件上下文应该在另一个指定插件之前执行,你不能在同一个插件上同时使用 .before() 和 .after()。config.resolve .plugin(name) .before(otherName)// 例如config.resolve .plugin(‘beta’) .use(BetaWebpackPlugin) .end() .plugin(‘alpha’) .use(AlphaWebpackPlugin) .before(‘beta’);配置 resolve 插件: 在之后调用指定当前插件上下文应该在另一个指定插件之后执行,你不能在同一个插件上同时使用 .before() 和 .after()。config.resolve .plugin(name) .after(otherName)// 例如config.resolve .plugin(‘beta’) .after(‘alpha’) .use(BetaWebpackTemplate) .end() .plugin(‘alpha’) .use(AlphaWebpackPlugin);配置 nodeconfig.node : ChainedMapconfig.node .set(’__dirname’, ‘mock’) .set(’__filename’, ‘mock’);配置 devServerconfig.devServer : ChainedMap配置 devServer allowedHostsconfig.devServer.allowedHosts : ChainedSetconfig.devServer.allowedHosts .add(value) .prepend(value) .clear()配置 devServer: 速记方法config.devServer .bonjour(bonjour) .clientLogLevel(clientLogLevel) .color(color) .compress(compress) .contentBase(contentBase) .disableHostCheck(disableHostCheck) .filename(filename) .headers(headers) .historyApiFallback(historyApiFallback) .host(host) .hot(hot) .hotOnly(hotOnly) .https(https) .inline(inline) .info(info) .lazy(lazy) .noInfo(noInfo) .open(open) .openPage(openPage) .overlay(overlay) .pfx(pfx) .pfxPassphrase(pfxPassphrase) .port(port) .progress(progress) .proxy(proxy) .public(public) .publicPath(publicPath) .quiet(quiet) .setup(setup) .socket(socket) .staticOptions(staticOptions) .stats(stats) .stdin(stdin) .useLocalIp(useLocalIp) .watchContentBase(watchContentBase) .watchOptions(watchOptions)配置 moduleconfig.module : ChainedMap配置 module: 速记方法config.module : ChainedMapconfig.module .noParse(noParse)配置 module rules: 速记方法config.module.rules : ChainedMapconfig.module .rule(name) .test(test) .pre() .post() .enforce(preOrPost)配置 module rules uses (loaders): 创建config.module.rules{}.uses : ChainedMapconfig.module .rule(name) .use(name) .loader(loader) .options(options)// Exampleconfig.module .rule(‘compile’) .use(‘babel’) .loader(‘babel-loader’) .options({ presets: [’@babel/preset-env’] });配置 module rules uses (loaders): 修改选项config.module .rule(name) .use(name) .tap(options => newOptions)// 例如config.module .rule(‘compile’) .use(‘babel’) .tap(options => merge(options, { plugins: [’@babel/plugin-proposal-class-properties’] }));配置 module rules oneOfs (条件 rules)config.module.rules{}.oneOfs : ChainedMap<Rule>config.module .rule(name) .oneOf(name)// 例如config.module .rule(‘css’) .oneOf(‘inline’) .resourceQuery(/inline/) .use(‘url’) .loader(‘url-loader’) .end() .end() .oneOf(’external’) .resourceQuery(/external/) .use(‘file’) .loader(‘file-loader’)合并配置webpack-chain 支持将对象合并到配置实例,改实例类似于 webpack-chain 模式 布局的布局。 请注意,这不是 webpack 配置对象,但您可以再将webpack配置对象提供给webpack-chain 以匹配器布局之前对其进行转换。config.merge({ devtool: ‘source-map’ });config.get(‘devtool’) // “source-map"config.merge({ [key]: value, amd, bail, cache, context, devtool, externals, loader, mode, parallelism, profile, recordsPath, recordsInputPath, recordsOutputPath, stats, target, watch, watchOptions, entry: { [name]: […values] }, plugin: { [name]: { plugin: WebpackPlugin, args: […args], before, after } }, devServer: { [key]: value, clientLogLevel, compress, contentBase, filename, headers, historyApiFallback, host, hot, hotOnly, https, inline, lazy, noInfo, overlay, port, proxy, quiet, setup, stats, watchContentBase }, node: { [key]: value }, optimizations: { concatenateModules, flagIncludedChunks, mergeDuplicateChunks, minimize, minimizer, namedChunks, namedModules, nodeEnv, noEmitOnErrors, occurrenceOrder, portableRecords, providedExports, removeAvailableModules, removeEmptyChunks, runtimeChunk, sideEffects, splitChunks, usedExports, }, performance: { [key]: value, hints, maxEntrypointSize, maxAssetSize, assetFilter }, resolve: { [key]: value, alias: { [key]: value }, aliasFields: […values], descriptionFields: […values], extensions: […values], mainFields: […values], mainFiles: […values], modules: […values], plugin: { [name]: { plugin: WebpackPlugin, args: […args], before, after } } }, resolveLoader: { [key]: value, alias: { [key]: value }, aliasFields: […values], descriptionFields: […values], extensions: […values], mainFields: […values], mainFiles: […values], modules: […values], moduleExtensions: […values], packageMains: […values], plugin: { [name]: { plugin: WebpackPlugin, args: […args], before, after } } }, module: { [key]: value, rule: { [name]: { [key]: value, enforce, issuer, parser, resource, resourceQuery, test, include: […paths], exclude: […paths], oneOf: { [name]: Rule }, use: { [name]: { loader: LoaderString, options: LoaderOptions, before, after } } } } }})条件配置当使用的情况下工作ChainedMap和ChainedSet,则可以使用执行条件的配置when。您必须指定一个表达式 when(),以评估其真实性或虚假性。如果表达式是真实的,则将使用当前链接实例的实例调用第一个函数参数。您可以选择提供在条件为假时调用的第二个函数,该函数也是当前链接的实例。// 示例:仅在生产期间添加minify插件config .when(process.env.NODE_ENV === ‘production’, config => { config .plugin(‘minify’) .use(BabiliWebpackPlugin); });// 例:只有在生产过程中添加缩小插件,否则设置devtool到源映射config .when(process.env.NODE_ENV === ‘production’, config => config.plugin(‘minify’).use(BabiliWebpackPlugin), config => config.devtool(‘source-map’) );检查生成的配置您可以使用检查生成的webpack配置config.toString()。这将生成配置的字符串化版本,其中包含命名规则,用法和插件的注释提示:config .module .rule(‘compile’) .test(/.js$/) .use(‘babel’) .loader(‘babel-loader’);config.toString();{ module: { rules: [ / config.module.rule(‘compile’) / { test: /.js$/, use: [ / config.module.rule(‘compile’).use(‘babel’) / { loader: ‘babel-loader’ } ] } ] }}默认情况下,如果生成的字符串包含需要的函数和插件,则不能直接用作真正的webpack配置。为了生成可用的配置,您可以通过__expression在其上设置特殊属性来自定义函数和插件的字符串化方式:class MyPlugin {}MyPlugin.__expression = require('my-plugin');function myFunction () {}myFunction.__expression = require('my-function');config .plugin(’example’) .use(MyPlugin, [{ fn: myFunction }]);config.toString();/{ plugins: [ new (require(‘my-plugin’))({ fn: require(‘my-function’) }) ]}/通过其路径指定的插件将require()自动生成其语句:config .plugin(’env’) .use(require.resolve(‘webpack/lib/ProvidePlugin’), [{ jQuery: ‘jquery’ }])config.toString();{ plugins: [ new (require(’/foo/bar/src/node_modules/webpack/lib/EnvironmentPlugin.js’))( { jQuery: ‘jquery’ } ) ]}您还可以调用toString静态方法Config,以便在字符串化之前修改配置对象。Config.toString({ …config.toConfig(), module: { defaultRules: [ { use: [ { loader: ‘banner-loader’, options: { prefix: ‘banner-prefix.txt’ }, }, ], }, ], },}){ plugins: [ / config.plugin(‘foo’) */ new TestPlugin() ], module: { defaultRules: [ { use: [ { loader: ‘banner-loader’, options: { prefix: ‘banner-prefix.txt’ } } ] } ] }} ...

December 28, 2018 · 6 min · jiezi

构建多页面应用——模板

因为大多数人都比较喜欢,或者说倾向于用js操作现有的html代码块,而不喜欢用js来生成html代码块,之后再来操作它。很明显的一点儿就是前者清晰明了,后者不是那么直观。因此在开发中,我们会接触到模板后者模板引擎这样概念。我们比较常见的就是*.html模板,Java开发中的*.jsp,php开发中的*.php,还有用于node.js的*.ejs 和 .jade(以及它的最新版本.pug)。这里,着重说一下html和pug。如何使用html-webpack-plugin的模板和注意事项html-webpack-plugin 支持为生成的页面指定模板,我们可以直接使用配置项为template,那么这个指定的html模板应该如何操作,或者说应该怎么操作,才能达到灵活多变的特性。使用webapcK做多页面应用的构建,我们当然是希望它能够实现构建单页面应用那样的模块化处理。我们从构建建多页面应用知道了构建多页面应用,可以实现js代码的模块化,从构建多页面应用——单个页面的处理,知道了构建多页面应用可以实现css代码的模块化。那么,构建多页面应用能实现html代码的模块化吗?当然可以。在上一篇文章中,有一个title不能注入到生成的页面的问题,但是html-webpack-plugin插件可以解析<%= htmlWebpackPlugin.options.title %>这样的语法,它内部可以写js语法,同样它也解决了title不能注入到生成的页面的问题,但它有一个限制条件就是只能使用在被当作模板的html文件中,其它的代码块无法使用,所以关于html代码块的模块化,我们可以在模板文件上下下功夫。使用过jQuery的同学都知道,我们可以使用$(’#container’).load(’./pages/partial.html’)的方法引入一个html代码块。而在webapck中,我们可以使用require(’./pages/commons/header.html’)的语法引入一个html代码块,但是webapck使用require引入的是一个文件流,不能和html模板文件相融合。所以这里要引入html-loader来做处理,它可以将流文件转化为字符串,这样就可以在html模板文件中使用了。基于html-webpack-plugin的模板的实际操作首先,本文中的构建多页面应用的项目目录图:├── src │ ├── common // 公用的模块 │ │ ├── a.js // 引用了a.css,模块header.css,container.css, footer.css │ │ ├── b.js // 引用了b.css │ │ ├── c.js // 引用了c.css │ │ ├── d.js ├── pages // html代码块 │ ├── template.html // 模板文件 │ ├── commons │ │ ├── header.html │ │ ├── footer.html │ │ ├── container.html ├── assets // 静态资源 │ ├── 19224132.jpg // 用来做页面图标 │ ├── css │ │ ├── a.css │ │ ├── b.css │ │ ├── c.css ├── assets // 静态资源 │ ├── 19224132.jpg // 用来做页面图标 │ ├── css │ │ ├── a.css │ │ ├── b.css │ │ ├── c.css │ │ ├── main.css │ │ ├── abutus.css │ │ ├── footer.css │ │ ├── container.css │ │ ├── header.css │ ├── uttils // 工具 │ │ ├── load.js // 工具代码load.js │ ├── index.js // 主模块index.js (包含a.js, b.js, c.js, d.js),引用了main.css │ ├── aboutUs.js // 主模块aboutus.js (包含a.js, b.js),引用了main.css, aboutus.css │ ├── contactUs.js // 主模块contactus.js (包含a.js, c.js),引用了main.css ├── webpack.config.js // css js 和图片资源 ├── package.json ├── yarn.lock新增了pages目录,它里面包含了html-webpack-plugin所需的模板文件和组成模板的各个模块文件。根据上文的分析,以及讲述需要,我们定义了一个公用的模板文件template.html,代码如下:<!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><%= htmlWebpackPlugin.options.title %></title></head><body> <!– header –> <!– <%= require(‘html-loader!./commons/header.html’) %> –> <%= require(’./commons/header.html’) %> <!– container –> <!– <%= require(‘html-loader!./commons/container.html’) %> –> <%= require(’./commons/container.html’) %> <!– footer –> <!– <%= require(‘html-loader!./commons/footer.html’) %> –> <%= require(’./commons/footer.html’) %></body></html>注:这里可能有的人会有个疑惑。因为熟悉构建单页面应用的人都将html-loader放到webapck.config.js文件中,这样可以一次配置,终身受用,如果你也像构建单页面应用那样操作的话,你需要小心再小心,因为你不加控制的话,html-loader 也会处理template.html,再加之<%= htmlWebpackPlugin.options.title %>是html-webpack-plugin插件的独有语法,这样在webapck编译的过程中,因为html-loader先执行,所以会将它转化为字符串,而html-webpack-plugin后执行,所以,这样的情况下,webpack生成的html页面不是你所需要的。解决方法也很简单你,只需要使用include选项即可(在webapck中使用include和exclude可以是loader的处理更精确,加开快编译的速度)。当然,根据世界开发需要,我们可根据页面的不同设计效果和组成页面的不同模块来定义不同的html模板。而对于哪些组成html模板的html模块,我们可以像写普通的html代码块那样,如:header.html,它的代码如下:<div class=“header”> <ul> <li><a href=“index.html”>首页</a></li> <li><a href=“aboutus.html”>关于我们</a></li> <li><a href=“contactus.html”>联系我们</a></li> </ul></div>如果你要对生成的html页面压缩,可以使用html-webpack-plugin的minify选项。这样,构建页面应用的js、css、html代码的模块化就都实现了。源代码可参考webpack4.x multi-page文章到这里,基本算是完成了这一篇文章的目的。但是如果你想对html代码进行更加细粒度的处理,可以考虑ejs或者pug。正如本文开始说的那样,下面就只简单的介绍pug的使用。用pug对html代码进行细粒度的操作这里,需要使用到的是pug-loader,它的作用和html-loader类似,只不过它有自己的语法,要经过从pug的语法到html的转换过程。但是它有更灵活的语法,可以让我们的页面代码更简洁。有些内容只用语言可能太空洞,还是用代码还解释。首先,模板的代码:doctype htmlhtml(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= htmlWebpackPlugin.options.title body // header include ./commons/header.pug // container include ./commons/container.pug // footer include ./commons/footer.pug注:因为这里使用了pug的语法,所以要使用pug依赖包,它实现的是pug语法到html的转换,而pug-loader只是起到了一个加载解析的作用,所以使用前,我们要执行如下的安装命令:yarn add -D pug pug-loader因为pug不仅可以使用包含,还可以使用集成,扩展,迭代和混合的特性,可以让我们随心所欲的对html实现模块化,所以深受开发者的喜爱。它们的具体使用可参考pug 官方文档。具体示例可参考webpack3.x multi-page的源代码。本篇文章要介绍的内容,到这里是真的结束了。当然,还有很多东西没有说完,如果需要可关注后续的文章。构建多页面应用系列文章webpack 构建多页面应用——初探构建多页面应用——单个页面的处理构建多页面应用——模板 ...

December 21, 2018 · 2 min · jiezi

构建多页面应用——单个页面的处理

在看这篇文章之前,需要你对构建多页面应用有一定的基础认识,如果没有的话,可以先参考这篇文章webpack 构建多页面应用。多页面应用是由一个个独立的页面组成。因此,细粒度的处理一个个单页面是构建单页面框架之后的一个重要实现。因为所涵盖的知识点较碎,所以就不按照页面的位置结合组成元素来讲,如:head, body, script等。这里主要介绍head。因为script操作其实就是上一篇文章中已经介绍过的js操作,而body因为内容较多,需要另起一篇文章。页面的头部在上一篇文章中,我们讲述了如何用html-webpack-plugin 生成一个html文件,其中使用了两个配置项chunks,filename,前者指代页面所要引入的js模块,也就是我们常见的html页面中的<script src="…"></script>形式,后者指代文件的名字。那么,在这一部分,要说的就是如何给不同的页面配置生成不同的页面<head>…</head>。我们都知道页面头部包括title、link/style、meta、script 这四部分组成,尤其前三者居多。当然,在web前端开发中js很强大,我们可以用js直接控制,在不同页面的入口js文件中写相应的js代码。这种方法虽然可行,但维护起来比较麻烦,当你修改的时候,你需要查找一个个页面。相对来讲,使用html-webpack-plugin提供的配置项,会使你的开发工作变得简单起来。html-webpack-plugin 插件的配置项title 选项可以为页面指定名字,meta 选项可以为页面指定html文档关联信息,如:描述,作者等,favicon 可以为页面添加一个小图标。 修改 webpack.config.js,代码如下:…nnew HtmlWebapckPlugin({ /* inital page / filename: ‘index.html’, chunks: [‘index’], / page head */ title: ‘index’, meta: { ‘description’: ‘这是首页’, ‘keywords’: ‘webpack, multi-page, 首页’, ‘author’: ‘https://github.com/lvzhenbang/ }, favicon: ‘./assets/19884132.jpg’})…这样头部常用的三个元素我们已经解决了两个。那么接下来就是解决link这个元素的。注:有一个比较特殊的就是html页面图标<link rel=“shortcut icon” href=“19884132.jpg”> ,我们使用 html-webpack-plugin 插件的 favicon 选项已经解决。link 和 style 部分的处理这两个元素常常被用来处理样式。link 处理外部样式,style 处理内联样式。注:很多人会误解,或曲解,这里的样式处理是这样的:在定义的页面入口文件,或者页面入口文件引用的文件中,引入css文件,webapck会将这些样式以内联的形式或者link的形式注入到生成的html页面中。这样我们的应用的目录结构就变成如下这样(本片文章使用如下的目录结构,它也介绍了各个js文件对css文件的引用):├── src │ ├── common // 公用的模块 │ │ ├── a.js // 引用了a.css │ │ ├── b.js // 引用了b.css │ │ ├── c.js // 引用了c.css │ │ ├── d.js ├── assets // 静态资源 │ ├── 19224132.jpg // 用来做页面图标 │ ├── css │ │ ├── a.css │ │ ├── b.css │ │ ├── c.css │ │ ├── main.css │ │ ├── abutus.css │ ├── uttils // 工具 │ │ ├── load.js // 工具代码load.js │ ├── index.js // 主模块index.js (包含a.js, b.js, c.js, d.js),引用了main.css │ ├── aboutUs.js // 主模块aboutus.js (包含a.js, b.js),引用了main.css, aboutus.css │ ├── contactUs.js // 主模块contactus.js (包含a.js, c.js),引用了main.css ├── webpack.config.js // css js 和图片资源 ├── package.json ├── yarn.lock处理为内联样式如果是webpack3.x 推荐使用 css-loader,style-loader,extract-text-webpack-plugin;如果是webapck4.x推荐使用的 css-loader, mini-css-extract-plugin。webpack3.x与webapck4.x都一样,修改webpack.config.js如下:…module: { rules: [ { test: /.css$/, use: [ ‘style-loader’, ‘css-loader’ ] } ]},…因为mini-css-extract-plugin是专门为webpack4.x设计的,如果webapck3.x使用它会报错。处理为外部链接(link)webpack3.x中webpack.config.js修改如下:…const ExtractTextPlugin = require(’extract-text-webapck-plugin’)… module: { rules: [ { test: /.css$/, use: ExtractTextPlugin.extract({ fallback: ‘style-loader’, use: ‘css-loader’ }) } ] }, plugins: [ … new ExtractTextPlugin({ filename: ‘[name].css’ }) ]webpack4.x中webpack.config.js修改如下:…const MiniCssExtractPlugin = require(‘mini-css-extract-plugin’)… module: { rules: [ { test: /.css$/, use: [ MiniCssExtractPlugin.loader, ‘css-loader’ ] } ] }, plugins: [ … MiniCssExtractPlugin() ],从js文件中分离出css文件,webpack3.x借助于extract-text-webpack-plugin,webpack4.x借助于mini-css-extract-plugin,前者给不同的css代码块命名需要在extract-text-webapck-plugin的示例中配置,它使用的是内置的CommonsChunkPlugin插件的拆分原则,后者不需要配置分离css代码块的名字选项,后者借助于SplitsChunkPlugin插件的拆分原则。所以,它们的分离形式与js代码块一致。webpack3.x为每个入口点生成了一个css文件,并提取了它们的公共代码生成了一个新的css文件;webapck4.x为每个入口生成了一个css文件,并提取并生成了这些文件相互之间的公共文件(它和前者不同,后者更精细化,只要是某一个或者几个文件有公共代码就提取出来,然后生成新的文件)。为什么将css文件和js文件分的这么细?是因为这样可以显著的减小首次加载页面时请求文件的大小(lazyload),但是这样做会增加HTTP的请求次数。在多页面应用的过程中,有的人喜欢将所有的css放在一个或两个文件中,而不是像本文中那样为每个页面生成一个css文件,包括它们之间的共用文件。但在多页面应用中,这样精密的细分也有其好处。相对来说,使用CommonsChunkPlugin拆分的css模块更合理些,而使用SplitsChunkPlugin拆分的css模块,则过于细化。至于如何取舍,还需要根据实际情况来定。当然,这里面还有一些小的问题需要优化,后期我会视情况来写相应的文章描述。源代码webpack3.x multi-pagewebpack4.x multi-page构建多页面应用系列文章webpack 构建多页面应用——初探构建多页面应用——单个页面的处理 ...

December 18, 2018 · 2 min · jiezi

前端构建:3类13种热门工具的选型参考

前言在前端项目的规模和复杂性不断提升的情况下,各类构建思想和相应工具层出不穷。本文竭己所能对比了当下13个构建工具,包括Browserify、Webpack、Rollup、Grunt、Gulp和Yeoman6个广为流行的工具,FIS、Athena、WeFlow和Cooking等4个国产工具,以及三大框架:React,Vue和Angular的官方脚手架。希望能在项目初期的构建工具选型上为大家提供些参考。全览构建工具可以分为三类:模块化打包类、任务流构建类和集合型工具类(脚手架)。其中最为突出的,当属用于模块化打包的Webpack和用于任务流构建的Gulp。下面是截至2018年11月28日某时某刻,GitHub上各个工具的Star数目(听说Star数目可以造假?好生无聊的家伙们!)。前端的构建一般包括JS转码(使用Babel转ES6或TypeScript自转等)、CSS转码(Less或Sass转Css)、代码或资源的合并与压缩,基础检查和各类测试等等。这些虽与本文关系密切,但都不在讨论的范围之内。原因有二:一是实现这些功能的都是某些插件,不是工具本身,各类构建工具都是直接或间接(调用以自己的模式封装后的插件)使用它们的;二是本文介绍的是,构建方向上的类别和各类别里不同工具间的差异,与具体的操作无关。模块化打包类现在的前端项目基本是模块化的,原因就不在这多说。而模块化意味着分散,无法直接用于呈现,因此需要进行相应的打包形成一个整体。有些执行环境(Node)能自动打包各个模块,而有些(浏览器)则因为技术或其它考虑需要自行操作。模块化打包工具就是为模块化项目在浏览器上的优化呈现而服务的。模块化打包的核心是:找出依赖,打包成体。各类工具的基本运行思路便是根据已有配置,从某个文件开始,递归的找出所有与其相关的依赖模块,打包成某种类型的可直接在浏览器中运行的一个或多个新文件。这之中还可以优化输出,以实现代码分离、异步加载和长效缓存等高级功能。Browserify官网 | GitHub正如其官网介绍的,Browserify会递归的分析,项目中所有使用require引入的JS模块(包括Node内置模块)并打包,使得Node类项目能在浏览器上运行。不过对于与项目有关的其它资源,比如Css和图片等,依然需要手动管理。虽然网上已有人编写了支持此些功能的插件,但这不仅违背了设计初衷,也使配置变得杂乱。而且对于要求越来越高的单页面应用来说,它能提供的助力着实已显疲惫。Webpack官网 | 中文 | GitHub稳定版已到v4.26.0,本文以此版本为据。另附加官方的对比文档。Webpack的设计思想新颖实用,社区活跃,功能强大全面,已经是针对前端各类资源的、目前最优秀的模块化管理和打包工具。它入门简单,基本的常用功能能很快上手,并用于实际开发。但精通不易,毕竟打包已是web开发中最重要的挑战之一,必然要耗费些许精力。学习尚且不易,介绍就更为困难,得要有一本书的厚度。所幸此节不是详细介绍,只是亮点阐述,善哉善哉。入门已趋简单掌握了构建的基本思路,任意工具的入门都是较为简单的(读者批:废话)。之所以强调Webpack入门简单,是为了减轻有意者学习之前的顾虑。一方面是它刚被推出时,由于自身的概念新颖而且文档不全面,使开发者处于懵懵懂懂的状态,总感觉离真谛还差些距离。另一方面是它的体系着实庞大,仔细想想都不免胆怯。笔者初次接触时便是这些个感受。但现在不一样。吃土的日子已经远去,啃草的梦想还会远吗?大家准备好镰刀!Webpack第四版在入门上的方便性体现在三方面。一是基础功能高度集成和约定优于配置思想:安装好Webpack及其CLI后便可直接打包JS和JSON文件,与Browserify一样简单。二是官方文档详细(而且有基本同步的中文版),无论是概念的解析、实际运用的示例还是接口的展示都十分完备。三是现在使用和介绍Webpack的人已经很多了,因此网上的各路资料和相应问题的解决方案都十分丰富。你还在犹豫?一切皆模块如从官网上截取的图片所示,在Webapck眼中一切文件(.js、.css、.jpg、.woff、.csv和.ts等除了某些用于下载的静态大文件外)都是模块,都能通过与JS相似的方式被打包,并安置于合适浏览器渲染的位置。真是十分优秀的立足点。以此思想便可囊括前端会使用到的几乎所有资源,可以十分方便的统一管理和安置,更为便捷和高效。而且此思想就是为单页面应用而生的。在Webpack的另一层意境中,一个asset(各类资源)是一个模块,一个component是一个模块,一个module也是一个模块。而单页面应用的特点,不就是应用的更新不是整个页面的更新,而是某个module或component或asset的更新吗?十分的契合。有人说Webpack的缺点在服务端渲染(或说多页面应用)上。喂喂,一来别人的目标本就不在此,二是多页面应用也不需要如此复杂的支持体系。高效的构建性能单页面应用或说需要构建才能展示的应用,相比多页面应用,从每次修改到重新呈现要多经历一个构建的阶段。实际操作中,如果项目庞大而构建性能不够优化,一个小小的修改(打印某值)都会消耗5秒以上的时间,对开发者来说真是个地狱!而优化的方法不外乎两点,一是开发者优化项目的构建思路,二是构建工具优化自身的构建性能。Webpack拥有较理想的构建性能。在开发阶段,当开启了Webpack的模块热替换之后(使用webpack-dev-server会自动开启),一旦检测到文件被修改,会在应用程序运行过程中通过冒泡捕获的方式最小化替换、添加或删除模块,而无需重新加载整个页面。类似Dom渲染中的回流:如果子元素发生的大小变化,会影响兄弟元素但不影响父元素,那么父元素及其它是无需重新绘制的。而且即便完全重新构建,也会保留先前的应用程序状态,减少等待时间。活跃的社区活跃的社区可以提升系统的丰富度,降低学习与使用的成本。Webapck社区十分活跃,应用于各种需求的插件都被一一封装而可直接使用(官方也统一展示和说明了一些常用的优秀的Loader和Plugin)。不单单是其它工具的高度协调,开发中的各个阶段:搭建本地服务器、集成测试等,以及与任务流工具(Gulp、Grunt)的集成等等方面的解决或最优方案,都是丰富和全面的。基本上可以想到的需求,在这个社区中,都能直接借鉴他人已有的成果。Rollup官网 | 中文 | GitHubRollup定位为一个JS模块打包器(明指JS),主要用来构建JS库,也可服务于一些无需代码拆分和动态导入的小型应用程序。能在Webpack已稳居打包之首的情况下杀出一条血路,得到Vue、D3、Three和React等著名库的青睐,想必其着手点和性能有过人之处。Rollup本身结构简单,需要的配置项也不多,再加文档全面,所以很容易上手并全部掌握。它使用ES6本身的Module语法作为自己的标准,而不是诸如CommonJS和AMD等以前的解决方案。这意味着按照Module标准编成的代码,不仅现在可以借助Rollup打包运行,未来更能在实现此标准的浏览器上直接运行。通过Module的特性,Rollup开创了Tree-shaking功能——清除没有在项目中使用到的代码。它基于显式的import和export语句的方式,通过静态分析,排除了任何未在实际中使用的代码,能极大的减少构建于已有模块的项目体积。再加上其构建基本不添加自身的控制代码,使打包后的文件真正的达到纯净二字。想想还有点痒痒,我挠挠裆部。与 Webpack 对比Rollup和Webpack因其定位和专注点是可以共同存在并相互支持的。正如Rollup官网所说的,Rollup更适合构建独立的JS库,而Webpack为资源丰富的应用程序。虽然Webpack也增加了自己的Tree-shaking功能,但在编译后的输出代码中,简单地运行自动minifier检测未使用的变量,得到的结果是不如原生的静态分析。更何况Webpack生成的代码一定是经过自己包装后的代码——将每个模块封装在一个函数中,再置于一个包中,通过浏览器能使用的require方式逐一执行这些模块。任务流构建类基于任务的构建行为,是不在乎操作对象是否为模块化的。这类工具的目标是通过配置来解放日常需要重复的工作——转化、合并压缩和单元测试等等。有人说:这些操作Webpack和Rollup不是也能做?是的,基本能做。实际上,在用模块化构建工具的开发中,很少会用到任务流构建工具。但这绝不是说任务流工具会被取代,也不会被取代,至少多页面应用需要。再说任务流工具是十分纯粹的自动化行为,与模块化打包工具立足点就不一样,何谈取代一说。Grunt官网 | 中文 | GitHubGrunt虽是老牌构建工具,但依然被许多知名项目如WordPress、Twitter和Jquery等使用,也拥有持续更新的完整生态圈和中文文档。它是通过配置驱动——通过获取到的JSON配置执行操作,来流水线式执行相应任务。虽然在学习成本和执行效率上不出众,但如果项目原本就是通过它自动化构建的,是没有必要迁移到其它工具的。// Grunt 的配置驱动示例module.exports = function(grunt) { grunt.initConfig({ jshint: { files: [‘Gruntfile.js’, ‘src//*.js’, ’test//.js’], options: { globals: { jQuery: true } } }, watch: { files: [’<%= jshint.files %>’], tasks: [‘jshint’] } }); grunt.loadNpmTasks(‘grunt-contrib-jshint’); grunt.loadNpmTasks(‘grunt-contrib-watch’); grunt.registerTask(‘default’, [‘jshint’]);};Gulp官网 | 中文 | GitHubGulp是新型的构建工具,虽与Grunt的功能相同,但其构建过程却有三大优势。代码驱动代码驱动即通过执行实际代码驱动程序执行,与常见的配置驱动不同(Webpack、Rollup和Grunt等都是配置驱动)。从任务流构建的角度上看,代码驱动相比配置驱动有三点好处:一是高度的灵活;二是没有过多的配置项,减少学习成本;三是更方便错误的断定和异常情况的调试。// Gulp 的代码驱动示例gulp.src(’./client/templates/.jade’) .pipe(jade()) .pipe(gulp.dest(’./build/templates’)) .pipe(minify()) .pipe(gulp.dest(’./build/minified_templates’));Node流Gulp作为后来者,充分利用NodeJS流的思想进行IO操作,极大增加了大型项目的构建速度。比方说转化Scss成Css,Grunt的操作流程是:读取Scss文件、转化成Css、存储到磁盘,读取Css、压缩处理最后存储到磁盘;而Gulp得操作是:读取Scss文件、转化成Css、压缩处理最后存储到磁盘。一步到位,无需多次的IO操作。简单明了Gulp有十分精简的API。你能想到各种类型的任务,基本是通过仅有的五个可链式操作的方法实现的吗?不仅仅是学习和使用方便,编写后的功能也是一目了然。虽然代码驱动相比配置驱动,需要自己写的代码增加,但一是没增加难度只是函数名的多次重写,二是相对代码驱动的好处来说可以忽略。集合型工具类集合型工具类便是常说的脚手架,也可以看作是以模块化或任务流工具为主体的,各类常用工具的高度封装。它是一个开箱即可用的集合体,类似前后端同构时代的后端框架。它会根据你的选择,生成一个完整的、已配置好各类工具的、具有某些特定代码约定的项目框架。这些配置几乎包揽前端开发的整个流程,甚至可以集成自动化部署等后端接口。官方框架React CLI | Vue CLI | Angular CLI集合型工具一般为单页面应用服务,而单页面应用需要使用某个前端框架。无论你是用React、Vue或Angular,还是其它框架,首先得想到它是否有官方脚手架。比如Vue有Vue CLI。一般推荐有官方脚手架的直接使用官方的。因为现代前端框架一般不单独运行,需结合官方提供的其它工具,比如路由、状态管理等。而且各个框架及配件更新不断,每次更新都可能导致与其它插件的兼容问题,新的功能可能需要某些特定插件才能发挥作用。这是一项工程,仅靠个人或某些团体很难照顾周全的。而各个框架又都有意识的通过官方脚手架来充分展示新的特性,降低学习和使用的成本。我们何乐而不为呢?Yeoman官网 | GitHubYeoman是一个专为现代前端而生的、灵活通用的脚手架工具。它的运作方式和其它脚手架不同。在安装好CLI后,需要找到一个符合要求的Generator(一个npm包,相当于脚手架),使用Yeoman运行安装,生成初始化的项目。你也可以自行配置,使用Yeoman封装成符合特定需求的Generator,并发布出去。等到下次,其他人或你自己,需要生成符合此要求的项目时,便可以直接安装并使用Yeoman生成。这样有明显的两点好处:一是节省体力。在开始一个有特定需求的新项目时,如果有老项目可借鉴,一般会直接复制相关文件。但这样的复制文件可能不纯粹,即增加体积又带来安全隐患。二是在社区的支持下,很多有特殊要求的脚手架,早已有人解决并发布成Generator,是没必要自己动手的。国内其它百度 - FIS - 官网 | GitHub 微信 - WeFlow - 官网 | GitHub 京东 - Athena - 官网 | GitHub 饿了么 - Cooking(名字与公司的性质相得益彰) - 官网 | GitHub作为程序员或至各行各业,在与年龄增长速度相当的压力下,工资的高低自然成为日常性的评定标准。但在同行老友的酒桌上或某个太阳异常温煦下的小道上,能使自己为自己而不是其他事骄傲的,也肯定是“老子之前做过些什么”之类的实际付出而不是物质方面的获得。因此能够成为被公司支持的、被众多人使用的、开源框架维护团队中的程序员,多少是更为幸福的一类。这些由国内各个前端团队开发的集合型脚手架,都是基于自用在实践中得到的最为符合本身需求的产品。里面的包含内容十分丰富,不仅仅是这以上提到的前端本职工作,还有与后端的集成方案或自动化部署配置等。且流程简化,开箱即可使用。不过这些笔者都没用过,也没有打算用。不是打趣,原因很现实,有识之士可以在文章下留言。不用却依然写出的原因倒是简单:宣传,宣传即赞许和期盼;凑数,凑到13种好立个多少浮夸的标题。总结个人观点,不喜请喷,但要和蔼可亲。如果是使用某个前端框架开发应用程序,推荐框架官方的脚手架。如果是自己头脑发热想开源个JS库,推荐Rollup打包。如果不是模块化项目,又需要自动化处理一些事情,推荐Gulp作为构建工具。如果项目有特殊要求或作为核心的部件比较稀有,可以先查看Yeoman上是否有符合要求的Generator,没有就只能自食其力。最后如果你处在已有自己脚手架的公司(比如饿了么),可能要按规章制度使用Cooking为自己的仕途烹煮些吃食。肚子真饿,这种宣传饿了么会返优惠券吗?最后,如果是自食其力的搭建前人没有的脚手架,推荐使用Yeoman发布,方便你我他。 ...

November 30, 2018 · 1 min · jiezi

Gulp 前端构建工具

Gulp是一个自动化的前端构建工具,具有一些优点:易于使用:通过代码优于配置的策略,Gulp 让简单的任务简单,复杂的任务可管理。构建快速:利用 Node.js 流的威力,你可以快速构建项目并减少频繁的 IO 操作。高质量的插件:Gulp 严格的插件指南确保插件如你期望的那样简洁高质得工作。易于学习:通过最少的 API,掌握 Gulp 毫不费力,构建工作尽在掌握:如同一系列流管道。1、Gulp的安装1、首先安装nodejs环境2、全局安装Gulpnpm install -g gulp3、项目中安装gulp切换目录到要使用gulp的项目,在命令中执行npm install gulp4、若想在安装的时候把gulp写进项目package.json中,需要在命令行中加上–save-devnpm install –save-dev gulp对于全局安装gulp后还要在项目内再进行一次gulp安装的原因是为了版本的灵活性。2、使用2.1、创建主文件创建一个主文件夹,命名为gulpfile.js,同时将该文件放于使用gulp的项目目录中。gulpfile.js文件用于定义我们的任务,以下为一个简单的gulpfile.js文件的示例:var gulp = require(‘gulp’)gulp.task(‘default’,function(){ console.log(‘hello world’)});此时目录结构为:|–gulpfile.js|–node_modules| |–gulp|– package.json2.2、运行gulp任务切换到gulpfile.js文件的目录下,执行命令gulp如果要执行特定的任务,这执行命令:gulp task如果没有指定任务,则执行任务名为default的默认任务。3、Gulp 的APIgulp常用的API有4个:gulp.task()gulp.src()gulp.dest()gulp.watch()3.1 gulp.src()Gulp是以stream为媒介的,它不需要频繁的生成临时文件,gulp.src() 正是用于获取流的。这个流的内容是一个虚拟文件对象流(Vinyl files),虚拟文件对象中存储着原始文件的路径、文件名、内容等信息。语法为:gulp.src(globs[, options])globs参数是文件匹配模式(类似正则表达式),用来匹配文件路径(包括文件名),当然这里也可以直接指定某个具体的文件路径。当有多个匹配模式时,该参数可以为一个数组。options为可选参数。通常情况下我们不需要用到。gulp匹配具有一定的规则及文件匹配技巧:匹配文件路径中的0个或多个字符,但不会匹配路径分隔符,除非路径分隔符出现在末尾**匹配路径中的0个或多个目录及其子目录,需要单独出现,即它左右不能有其他东西了。如果出现在末尾,也能匹配文件。? 匹配文件路径中的一个字符(不会匹配路径分隔符)[…] 匹配方括号中出现的字符中的任意一个,当方括号中第一个字符为^或!时,则表示不匹配方括号中出现的其他字符中的任意一个,类似js正则表达式中的用法!(pattern|pattern|pattern) 匹配任何与括号中给定的任一模式都不匹配的?(pattern|pattern|pattern) 匹配括号中给定的任一模式0次或1次,类似于js正则中的(pattern|pattern|pattern)?+(pattern|pattern|pattern) 匹配括号中给定的任一模式至少1次,类似于js正则中的(pattern|pattern|pattern)+(pattern|pattern|pattern) 匹配括号中给定的任一模式0次或多次,类似于js正则中的(pattern|pattern|pattern)@(pattern|pattern|pattern) 匹配括号中给定的任一模式1次,类似于js正则中的(pattern|pattern|pattern)有多种匹配模式时可以使用数组://使用数组的方式来匹配多种文件gulp.src([‘js/.js’,‘css/.css’,’.html’])使用数组还可以很方便的使用排除模式,在数组中的单个匹配模式前加上!即是排除模式,在匹配结果中排除这个匹配,但是不能在数组中的第一个元素中使用排除模式。gulp.src([.js,’!b.js’]) //匹配所有js文件,但排除掉以b开头的js文件gulp.src([’!b.js’,.js]) //不会排除任何文件,因为排除模式不能出现在数组的第一个元素中3.2 gulp.task()gulp.task方法用来定义任务:gulp.task(name[, deps], fn)name 为任务名deps 是当前定义的任务需要依赖的其他任务,为一个数组。当前定义的任务会在所有依赖的任务执行完毕后才开始执行。如果没有依赖,则可省略这个参数fn 为任务函数,我们把任务要执行的代码都写在里面。该参数也是可选的。定义一个有依赖的任务:gulp.task(‘mytask’, [‘array’, ‘of’, ’task’, ’names’], function() { // Do something});gulp.task()任务的执行顺序为:当任务相互之间没有依赖时,任务会按照书写的顺序来执行,如果有依赖的话则会先执行依赖的任务(当依赖的任务是同步的)。当依赖的任务为异步时,gulp并不会等依赖执行完,而是紧接着执行后续的任务。如果想要在异步任务执行完再执行后续的任务,有三种方式:1、异步操作完成时设置回调函数通知gulp异步任务已经完成,此时的回调函数就是任务函数的第一个参数gulp.task(‘one’,function(cb){ //cb为任务函数提供的回调,用来通知任务已经完成 //one是一个异步执行的任务 setTimeout(function(){ console.log(‘one is done’); cb(); //执行回调,表示这个异步任务已经完成 },5000);});//这时two任务会在one任务中的异步操作完成后再执行gulp.task(’two’,[‘one’],function(){ console.log(’two is done’);});2、定义任务时返回一个流对象。适用于任务就是操作gulp.src获取到的流的情况。gulp.task(‘one’,function(cb){ var stream = gulp.src(‘client//.js’) .pipe(dosomething()) //dosomething()中有某些异步操作 .pipe(gulp.dest(‘build’)); return stream;});gulp.task(’two’,[‘one’],function(){ console.log(’two is done’);});3、返回一个promise对象var Q = require(‘q’); //一个著名的异步处理的库 https://github.com/kriskowal/qgulp.task('one',function(cb){ var deferred = Q.defer(); // 做一些异步操作 setTimeout(function() { deferred.resolve(); }, 5000); return deferred.promise;});gulp.task(’two’,[‘one’],function(){ console.log(’two is done’);});3.3 gulp.dest()gulp.src() 用于写文件,其语法为:语法为:gulp.dest(path[,options])path为写入文件的路径options为一个可选的参数对象,通常我们不需要用到gulp的使用流程如下:首先通过gulp.src()方法获取到我们想要处理的文件流,然后把文件流通过pipe方法导入到gulp的插件中,最后把经过插件处理后的流再通过pipe方法导入到gulp.dest()中,gulp.dest()方法则把流中的内容写入到文件中,这里首先需要弄清楚的一点是,我们给gulp.dest()传入的路径参数,只能用来指定要生成的文件的目录,而不能指定生成文件的文件名,它生成文件的文件名使用的是导入到它的文件流自身的文件名,所以生成的文件名是由导入到它的文件流决定的,即使我们给它传入一个带有文件名的路径参数,然后它也会把这个文件名当做是目录名,例如:var gulp = require(‘gulp’);gulp.src(‘script/jquery.js’) .pipe(gulp.dest(‘dist/foo.js’));//最终生成的文件路径为 dist/foo.js/jquery.js,而不是dist/foo.js生成的文件与传入gulp.dest()的参数有一定的联系,gulp.dest(path)生成的文件路径是我们传入的path参数后面再加上gulp.src()中有通配符开始出现的那部分路径。var gulp = reruire(‘gulp’);//有通配符开始出现的那部分路径为 **/.jsgulp.src(‘script//.js’) .pipe(gulp.dest(‘dist’)); //最后生成的文件路径为 dist/**/.js//如果 /.js 匹配到的文件为 jquery/jquery.js ,则生成的文件路径为 dist/jquery/jquery.js通过指定gulp.src()方法配置参数中的base属性,我们可以更灵活的来改变gulp.dest()生成的文件路径。当我们没有在gulp.src()方法中配置base属性时,base的默认值为通配符开始出现之前那部分路径,例如:gulp.src(‘app/src//.css’) //此时base的值为 app/src实际上,gulp.dest()所生成的文件路径的规则,其实也可以理解成,用我们给gulp.dest()传入的路径替换掉gulp.src()中的base路径,最终得到生成文件的路径。gulp.src(‘app/src//.css’) //此时base的值为app/src,也就是说它的base路径为app/src //设该模式匹配到了文件 app/src/css/normal.css .pipe(gulp.dest(‘dist’)) //用dist替换掉base路径,最终得到 dist/css/normal.css所以改变base路径后,gulp.dest()生成的文件路径也会改变gulp.src(script/lib/.js) //没有配置base参数,此时默认的base路径为script/lib //假设匹配到的文件为script/lib/jquery.js .pipe(gulp.dest(‘build’)) //生成的文件路径为 build/jquery.jsgulp.src(script/lib/.js, {base:‘script’}) //配置了base参数,此时base路径为script //假设匹配到的文件为script/lib/jquery.js .pipe(gulp.dest(‘build’)) //此时生成的文件路径为 build/lib/jquery.js 用gulp.dest()把文件流写入文件后,文件流仍然可以继续使用3.4 gulp.watch()gulp.watch()用来监视文件的变化,当文件发生变化后,我们可以利用它来执行相应的任务,例如文件压缩等.gulp.watch(glob[, opts], tasks)glob 为要监视的文件匹配模式,规则和用法与gulp.src()方法中的glob相同。opts 为一个可选的配置对象,通常不需要用到tasks 为文件变化后要执行的任务,为一个数组另一种使用方式为:gulp.watch(glob[, opts, cb])glob 和 opts 参数与第一种用法相同cb 为一个函数。每当监视的文件发生变化时,就会调用这个函数,并且会给它传入一个对象,该对象包含了文件变化的一些信息,type属性为变化的类型,可以是added,changed,deleted;path属性为发生变化的文件的路径gulp.watch(‘js/**/.js’, function(event){ console.log(event.type); //变化类型 added为新增,deleted为删除,changed为改变 console.log(event.path); //变化的文件的路径}); 4、常用插件4.1 自动加载插件(gulp-load-plugins)命令:npm install –save-dev gulp-load-plugins4.2 重命名(gulp-rename)命令:npm install –save-dev gulp-rename4.3 js文件压缩(gulp-uglify)命令:npm install –save-dev gulp-uglify4.4 css文件压缩(gulp-minify-css)命令:npm install –save-dev gulp-minify-css4.5 html文件压缩(gulp-minify-html)命令:npm install –save-dev gulp-minify-html4.6 js代码检查(gulp-jshint)命令:npm install –save-dev gulp-jshint4.7 文件合并(gulp-concat)命令:npm install –save-dev gulp-concat4.8 less 和 sass 编译(gulp-less/gulp-sass)命令:npm install –save-dev gulp-lessnpm install –save-dev gulp-sass4.9 图片压缩(gulp-imagemin)命令:npm install –save-dev gulp-imagemin4.10 自动刷新(gulp-livereload)命令:npm install –save-dev gulp-livereload ...

November 26, 2018 · 1 min · jiezi