cainiaoblog 23.2.171
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CNAME +1 -0
- package/MP_verify_o6dHCK5mIkxNpzvZ.txt +1 -0
- package/README.md +8 -0
- package/about/index.html +54 -0
- package/ads.txt +1 -0
- package/app/manual.html +104 -0
- package/appConfigs/config +1 -0
- package/archives/2018/index.html +48 -0
- package/archives/2019/index.html +48 -0
- package/archives/2019/page/2/index.html +48 -0
- package/archives/2020/index.html +48 -0
- package/archives/2021/index.html +48 -0
- package/archives/2022/index.html +48 -0
- package/archives/2023/index.html +48 -0
- package/archives/index.html +48 -0
- package/archives/page/2/index.html +48 -0
- package/archives/page/3/index.html +48 -0
- package/article/10047.html +148 -0
- package/article/10164.html +64 -0
- package/article/10624.html +436 -0
- package/article/10745.html +586 -0
- package/article/11674.html +71 -0
- package/article/11939.html +72 -0
- package/article/12238.html +86 -0
- package/article/1230.html +533 -0
- package/article/13265.html +69 -0
- package/article/13823.html +78 -0
- package/article/14125.html +72 -0
- package/article/14192.html +561 -0
- package/article/14375.html +85 -0
- package/article/14744.html +83 -0
- package/article/14936.html +451 -0
- package/article/1518.html +250 -0
- package/article/15428.html +71 -0
- package/article/15518.html +288 -0
- package/article/15569.html +418 -0
- package/article/1566.html +122 -0
- package/article/16017.html +156 -0
- package/article/16947.html +440 -0
- package/article/17096.html +82 -0
- package/article/17246.html +89 -0
- package/article/17575.html +350 -0
- package/article/179.html +757 -0
- package/article/18039.html +233 -0
- package/article/18071.html +375 -0
- package/article/18142.html +71 -0
- package/article/19909.html +160 -0
- package/article/20014.html +89 -0
- package/article/20021.html +389 -0
- package/article/20037.html +280 -0
- package/article/20056.html +95 -0
- package/article/20093.html +404 -0
- package/article/2021.html +607 -0
- package/article/20459.html +115 -0
- package/article/20613.html +263 -0
- package/article/20635.html +71 -0
- package/article/21317.html +71 -0
- package/article/21320.html +90 -0
- package/article/21782.html +548 -0
- package/article/22230.html +435 -0
- package/article/22237.html +347 -0
- package/article/22398.html +109 -0
- package/article/22422.html +270 -0
- package/article/22565.html +300 -0
- package/article/23046.html +250 -0
- package/article/23083.html +215 -0
- package/article/23195.html +297 -0
- package/article/23301.html +835 -0
- package/article/23589.html +285 -0
- package/article/23655.html +338 -0
- package/article/23864.html +76 -0
- package/article/23914.html +96 -0
- package/article/24169.html +135 -0
- package/article/24581.html +349 -0
- package/article/2475.html +500 -0
- package/article/24826.html +77 -0
- package/article/24924.html +247 -0
- package/article/25164.html +1495 -0
- package/article/25318.html +394 -0
- package/article/25710.html +1129 -0
- package/article/25734.html +83 -0
- package/article/26083.html +174 -0
- package/article/26147.html +283 -0
- package/article/26291.html +110 -0
- package/article/26354.html +71 -0
- package/article/26624.html +145 -0
- package/article/26639.html +298 -0
- package/article/26850.html +138 -0
- package/article/26940.html +258 -0
- package/article/27796.html +190 -0
- package/article/27800.html +161 -0
- package/article/28963.html +159 -0
- package/article/28976.html +247 -0
- package/article/29042.html +72 -0
- package/article/29596.html +81 -0
- package/article/29619.html +183 -0
- package/article/29623.html +329 -0
- package/article/29829.html +205 -0
- package/article/29abbd1c.html +112 -0
- package/article/30215.html +178 -0
- package/article/30552.html +78 -0
- package/article/30818.html +201 -0
- package/article/31330.html +364 -0
- package/article/3154.html +577 -0
- package/article/31695.html +348 -0
- package/article/31826.html +71 -0
- package/article/31843.html +132 -0
- package/article/32280.html +212 -0
- package/article/32431.html +74 -0
- package/article/32443.html +72 -0
- package/article/32732.html +122 -0
- package/article/3286.html +251 -0
- package/article/32960.html +198 -0
- package/article/33713.html +72 -0
- package/article/33899.html +615 -0
- package/article/34351.html +74 -0
- package/article/34372.html +89 -0
- package/article/34410.html +74 -0
- package/article/34431.html +118 -0
- package/article/3447.html +75 -0
- package/article/34474.html +142 -0
- package/article/34871.html +147 -0
- package/article/35397.html +175 -0
- package/article/3544.html +199 -0
- package/article/35503.html +115 -0
- package/article/35624.html +84 -0
- package/article/35697.html +384 -0
- package/article/35863.html +73 -0
- package/article/36037.html +253 -0
- package/article/36118.html +71 -0
- package/article/3621.html +119 -0
- package/article/36251.html +296 -0
- package/article/36642.html +352 -0
- package/article/37102.html +763 -0
- package/article/37336.html +351 -0
- package/article/37351.html +89 -0
- package/article/3760.html +294 -0
- package/article/37749.html +75 -0
- package/article/37789.html +83 -0
- package/article/38005.html +765 -0
- package/article/38009.html +284 -0
- package/article/38269.html +194 -0
- package/article/38344.html +88 -0
- package/article/38359.html +334 -0
- package/article/38423.html +379 -0
- package/article/38530.html +213 -0
- package/article/38743.html +268 -0
- package/article/38753.html +204 -0
- package/article/38881.html +71 -0
- package/article/39087.html +414 -0
- package/article/39309.html +84 -0
- package/article/39486.html +74 -0
- package/article/39637.html +306 -0
- package/article/39839.html +72 -0
- package/article/39937.html +152 -0
- package/article/39970.html +71 -0
- package/article/402.html +559 -0
- package/article/40698.html +71 -0
- package/article/40811.html +71 -0
- package/article/40964.html +462 -0
- package/article/41011.html +322 -0
- package/article/41152.html +78 -0
- package/article/41696.html +71 -0
- package/article/41717.html +281 -0
- package/article/41777.html +149 -0
- package/article/419.html +985 -0
- package/article/4233.html +136 -0
- package/article/4292.html +86 -0
- package/article/43302.html +96 -0
- package/article/43397.html +106 -0
- package/article/43724.html +281 -0
- package/article/43999.html +224 -0
- package/article/44426.html +77 -0
- package/article/44964.html +137 -0
- package/article/45004.html +123 -0
- package/article/4510.html +83 -0
- package/article/45156.html +320 -0
- package/article/45157.html +52 -0
- package/article/45267.html +87 -0
- package/article/45641.html +207 -0
- package/article/45960.html +75 -0
- package/article/46100.html +1262 -0
- package/article/46366.html +74 -0
- package/article/46771.html +96 -0
- package/article/46911.html +517 -0
- package/article/46948.html +137 -0
- package/article/46ee4850.html +349 -0
- package/article/47019.html +84 -0
- package/article/47532.html +94 -0
- package/article/4783.html +429 -0
- package/article/47972.html +72 -0
- package/article/48216.html +127 -0
- package/article/48252.html +185 -0
- package/article/48520.html +74 -0
- package/article/48722.html +75 -0
- package/article/48807.html +99 -0
- package/article/49126.html +323 -0
- package/article/50001.html +88 -0
- package/article/50058.html +151 -0
- package/article/50343.html +410 -0
- package/article/50510.html +82 -0
- package/article/51082.html +98 -0
- package/article/51677.html +111 -0
- package/article/5204.html +1085 -0
- package/article/52500.html +538 -0
- package/article/52689.html +259 -0
- package/article/52877.html +569 -0
- package/article/53125.html +191 -0
- package/article/53428.html +251 -0
- package/article/53524.html +74 -0
- package/article/53698.html +322 -0
- package/article/53803e6.html +93 -0
- package/article/53924.html +291 -0
- package/article/53962.html +253 -0
- package/article/54006.html +71 -0
- package/article/54018.html +88 -0
- package/article/54161.html +83 -0
- package/article/54178.html +69 -0
- package/article/54258.html +327 -0
- package/article/54295.html +214 -0
- package/article/54313.html +1976 -0
- package/article/54351.html +90 -0
- package/article/54604.html +80 -0
- package/article/54632.html +71 -0
- package/article/54912.html +756 -0
- package/article/54918.html +129 -0
- package/article/55119.html +136 -0
- package/article/55756.html +118 -0
- package/article/55769.html +136 -0
- package/article/55870.html +161 -0
- package/article/56139.html +92 -0
- package/article/56557.html +69 -0
- package/article/56604.html +69 -0
- package/article/56816.html +266 -0
- package/article/56926.html +153 -0
- package/article/57747.html +409 -0
- package/article/57965.html +475 -0
- package/article/58816.html +78 -0
- package/article/59059.html +435 -0
- package/article/59095.html +350 -0
- package/article/59349.html +244 -0
- package/article/59402.html +216 -0
- package/article/59448.html +55 -0
- package/article/59870.html +94 -0
- package/article/59877.html +208 -0
- package/article/5dbb6f41.html +86 -0
- package/article/6008.html +75 -0
- package/article/60327.html +126 -0
- package/article/6035.html +76 -0
- package/article/60428.html +71 -0
- package/article/60544.html +128 -0
- package/article/61319.html +95 -0
- package/article/61367.html +93 -0
- package/article/624d1918.html +292 -0
- package/article/62543.html +71 -0
- package/article/62887.html +169 -0
- package/article/63074.html +120 -0
- package/article/63266.html +311 -0
- package/article/63351.html +74 -0
- package/article/63542.html +104 -0
- package/article/63824.html +275 -0
- package/article/64051.html +653 -0
- package/article/64189b69.html +72 -0
- package/article/64354.html +75 -0
- package/article/64869.html +79 -0
- package/article/64923.html +596 -0
- package/article/65128.html +95 -0
- package/article/65295.html +79 -0
- package/article/6888.html +75 -0
- package/article/7065.html +84 -0
- package/article/7455.html +85 -0
- package/article/7a3ef8f.html +315 -0
- package/article/8140.html +188 -0
- package/article/8190.html +85 -0
- package/article/8225.html +78 -0
- package/article/84c9ccd6.html +159 -0
- package/article/8f9a9ae3.html +82 -0
- package/article/9174.html +73 -0
- package/article/9738.html +294 -0
- package/article/a4145266.html +71 -0
- package/article/bedea419.html +72 -0
- package/article/bf7e7421.html +78 -0
- package/article/c1867fbf.html +103 -0
- package/article/c5576490.html +70 -0
- package/article/dcb0db28.html +76 -0
- package/article/fc3b727a.html +79 -0
- package/atom.xml +445 -0
- package/baidusitemap.xml +1079 -0
- package/blocks/dns.txt +264 -0
- package/blocks/filter.txt +1070 -0
- package/blocks/script.js +42 -0
- package/categories/CDN/index.html +48 -0
- package/categories/IDE/index.html +48 -0
- package/categories/git/index.html +48 -0
- package/categories/hexo/index.html +48 -0
- package/categories//345/205/254/345/205/261/350/212/202/346/227/245/index.html +48 -0
- package/categories//345/210/206/344/272/253/index.html +48 -0
- package/categories//345/211/215/347/253/257/index.html +48 -0
- package/categories//345/211/215/347/253/257/page/2/index.html +48 -0
- package/categories//345/211/215/347/253/257//346/265/217/350/247/210/345/231/250/index.html +48 -0
- package/categories//345/211/215/347/253/257//346/265/217/350/247/210/345/231/250//347/275/221/347/273/234/345/215/217/350/256/256/index.html +48 -0
- package/categories//345/215/232/345/256/242/index.html +48 -0
- package/categories//345/267/245/345/205/267/index.html +48 -0
- package/categories//345/271/277/345/221/212/350/277/207/346/273/244/index.html +48 -0
- package/categories//345/271/277/345/221/212/350/277/207/346/273/244//350/247/206/351/242/221/347/224/265/350/247/206/345/260/217/350/257/264/346/274/253/347/224/273/350/265/204/350/256/257/351/237/263/344/271/220/index.html +48 -0
- package/categories//346/255/243/345/210/231/index.html +48 -0
- package/categories//346/265/217/350/247/210/345/231/250/index.html +48 -0
- package/categories//347/256/227/346/263/225/index.html +48 -0
- package/categories//350/265/204/350/256/257/index.html +48 -0
- package/categories//350/275/273/346/235/276/344/270/200/345/210/273/index.html +48 -0
- package/cdn/css/font-awesome.min.css +4 -0
- package/cdn/css/grids-responsive-min.css +7 -0
- package/cdn/css/normalize.css +349 -0
- package/cdn/css/pure-min.css +11 -0
- package/cdn/fonts/fontawesome-webfont.eot +0 -0
- package/cdn/fonts/fontawesome-webfont.svg +2671 -0
- package/cdn/fonts/fontawesome-webfont.ttf +0 -0
- package/cdn/fonts/fontawesome-webfont.woff +0 -0
- package/cdn/fonts/fontawesome-webfont.woff2 +0 -0
- package/cdn/js/Valine.min.js +17 -0
- package/cdn/js/canvas-nest.js +1 -0
- package/cdn/js/clipboard.min.js +7 -0
- package/cdn/js/crypto-js.js +25 -0
- package/cdn/js/jquery.min.js +2 -0
- package/css/default.css +1144 -0
- package/css/donate.css +338 -0
- package/css/hbe.style.css +749 -0
- package/css/style.css +2095 -0
- package/donate/index.html +39 -0
- package/google917dcf72f6e5c7f5.html +1 -0
- package/guestbook/index.html +68 -0
- package/hosts/ads.txt +149 -0
- package/hosts/flash.txt +9 -0
- package/hosts/github.txt +6 -0
- package/hosts/google.txt +16936 -0
- package/hosts/winrar.txt +2 -0
- package/img/alipay.svg +46 -0
- package/img/bitcoin.svg +135 -0
- package/img/blog.ico +0 -0
- package/img/blog.png +0 -0
- package/img/gaba.png +0 -0
- package/img/github.svg +1 -0
- package/img/like.svg +1 -0
- package/img/mp-mini.jpg +0 -0
- package/img/mp.png +0 -0
- package/img/paypal.svg +63 -0
- package/img/upy_logo.svg +59 -0
- package/img/wechat.svg +49 -0
- package/index.html +48 -0
- package/jd_root.txt +1 -0
- package/js/codeblock-resizer.js +51 -0
- package/js/dark.js +12 -0
- package/js/donate.js +87 -0
- package/js/fixedPage.js +106 -0
- package/js/gitment.browser.js +3751 -0
- package/js/search.js +86 -0
- package/js/share.js +60 -0
- package/js/smartresize.js +32 -0
- package/js/totop.js +12 -0
- package/kankan/index.html +77 -0
- package/lib/hbe.js +293 -0
- package/package.json +25 -0
- package/page/10/index.html +49 -0
- package/page/11/index.html +59 -0
- package/page/12/index.html +67 -0
- package/page/13/index.html +63 -0
- package/page/14/index.html +66 -0
- package/page/15/index.html +60 -0
- package/page/16/index.html +60 -0
- package/page/17/index.html +62 -0
- package/page/18/index.html +61 -0
- package/page/19/index.html +58 -0
- package/page/2/index.html +48 -0
- package/page/20/index.html +57 -0
- package/page/21/index.html +61 -0
- package/page/22/index.html +55 -0
- package/page/23/index.html +64 -0
- package/page/24/index.html +60 -0
- package/page/25/index.html +58 -0
- package/page/26/index.html +48 -0
- package/page/27/index.html +58 -0
- package/page/3/index.html +48 -0
- package/page/4/index.html +48 -0
- package/page/5/index.html +51 -0
- package/page/6/index.html +51 -0
- package/page/7/index.html +51 -0
- package/page/8/index.html +53 -0
- package/page/9/index.html +48 -0
- package/randomcall/README.html +1 -0
- package/randomcall/lucker.html +290 -0
- package/randomcall/lucky.png +0 -0
- package/robots.txt +22 -0
- package/search.xml +6740 -0
- package/sitemap.xml +2669 -0
- package/tags//345/216/237/345/210/233/index.html +48 -0
- package/tags//345/250/261/344/271/220/index.html +48 -0
- package/tags//346/211/213/345/206/231/index.html +48 -0
- package/tags//347/226/253/346/203/205/index.html +48 -0
- package/tags//350/275/254/350/275/275/index.html +48 -0
- package/tags//350/275/254/350/275/275/page/2/index.html +48 -0
- package/v.html +90 -0
|
@@ -0,0 +1,1495 @@
|
|
|
1
|
+
<!DOCTYPE html><html lang="zh-CN" data-dark><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport"><meta content="yes" name="apple-mobile-web-app-capable"><meta content="black-translucent" name="apple-mobile-web-app-status-bar-style"><meta content="telephone=no" name="format-detection"><meta name="description" content="前端壹菜鸟,记录工作难题,关注前端知识,收集精彩博文,做技术的搬运工"><meta name="msvalidate.01" content="0FE4D8B3381D3D87088996B886E1E2BD"><title>一步步从零开始用 webpack 搭建一个大型项目 | 前端壹菜鸟</title><link rel="stylesheet" type="text/css" href="//cdn.staticaly.com/gh/npljy/npljy.github.io/main/css/style.min.css"><link rel="stylesheet" type="text/css" href="//cdn.jsdelivr.net/npm/normalize.css@latest/normalize.min.css"><link rel="stylesheet" type="text/css" href="//cdn.jsdelivr.net/npm/purecss@latest/build/pure-min.min.css"><link rel="stylesheet" type="text/css" href="//cdn.jsdelivr.net/npm/purecss@latest/build/grids-responsive-min.css"><link rel="stylesheet" type="text/css" href="//cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css"><link rel="icon" mask="" sizes="any" href="//cdn.staticaly.com/gh/npljy/npljy.github.io/main/img/blog.ico"><link rel="Shortcut Icon" type="image/x-icon" href="//cdn.staticaly.com/gh/npljy/npljy.github.io/main/img/blog.ico"><link rel="apple-touch-icon" href="//cdn.staticaly.com/gh/npljy/npljy.github.io/main/img/blog.png"><link rel="apple-touch-icon-precomposed" href="//cdn.staticaly.com/gh/npljy/npljy.github.io/main/img/blog.png"><link rel="alternate" type="application/atom+xml" href="/atom.xml"><script type="text/javascript" src="//cdn.jsdelivr.net/npm/jquery@latest/dist/jquery.min.js"></script><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8385136408348258"></script><script src="https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js" async></script><script>(function(){
|
|
2
|
+
var bp = document.createElement('script');
|
|
3
|
+
var curProtocol = window.location.protocol.split(':')[0];
|
|
4
|
+
if (curProtocol === 'https'){
|
|
5
|
+
bp.src = 'https://zz.bdstatic.com/linksubmit/push.js';
|
|
6
|
+
}
|
|
7
|
+
else{
|
|
8
|
+
bp.src = 'http://push.zhanzhang.baidu.com/push.js';
|
|
9
|
+
}
|
|
10
|
+
var s = document.getElementsByTagName("script")[0];
|
|
11
|
+
s.parentNode.insertBefore(bp, s);
|
|
12
|
+
})();
|
|
13
|
+
</script><script>(function(){
|
|
14
|
+
var src = 'https://jspassport.ssl.qhimg.com/11.0.1.js?d182b3f28525f2db83acfaaf6e696dba';
|
|
15
|
+
document.write('<script src="' + src + '" id="sozz"><\/script>');
|
|
16
|
+
})();
|
|
17
|
+
</script><script>var _hmt=_hmt||[];(function(){var hm=document.createElement("script");hm.src='https://hm.baidu.com/hm.js?011f0b44e8452bfa57fbfa23c5fe7683';var s=document.getElementsByTagName("script")[0];s.parentNode.insertBefore(hm, s);})();
|
|
18
|
+
</script><script async src="https://www.googletagmanager.com/gtag/js?id=UA-150860401-1"></script><script>window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments);};gtag('js',new Date());gtag('config','UA-150860401-1');
|
|
19
|
+
</script><script type="text/javascript" src="//cdn.staticaly.com/gh/npljy/npljy.github.io/main/js/fixedPage.min.js"></script><meta name="generator" content="Hexo 6.3.0"></head><body><div class="body_container"><div id="header"><div class="site-name"><a id="logo" href="/.">前端壹菜鸟</a><p class="description">关注前端知识,收集精彩博文,做技术的搬运工</p></div><div id="nav-menu"><a class="current" href="/." target="_self"><i class="fa fa-home"> 首页</i></a><a href="/archives/" target="_self"><i class="fa fa-archive"> 归档</i></a><a href="/guestbook/" target="_self"><i class="fa fa-comments"> 留言</i></a><a href="/about/" target="_self"><i class="fa fa-user"> 关于</i></a><a href="https://laonongmin.online/" target="_target"><i class="fa fa-group"> 看看</i></a><a href="https://love.xuehuayu.cn/" target="_target"><i class="fa fa-heart"> LOVE</i></a></div></div><div class="pure-g" id="layout"><div class="pure-u-1 pure-u-md-3-4"><div class="content_container"><div class="post"><h1 class="post-title c-post">一步步从零开始用 webpack 搭建一个大型项目</h1><div class="post-meta"><span class="date">2019-12-23</span><span> | </span><span class="tag"><a href="/tags/%E8%BD%AC%E8%BD%BD/">转载 </a></span><span> | </span><span class="category"><a href="/categories/%E5%89%8D%E7%AB%AF/">前端 </a></span><span id="busuanzi_container_page_pv"> | <span id="busuanzi_value_page_pv"><i class="fa fa-spinner"></i></span><span> 阅读</span></span><span class="post-time"><span class="post-meta-item-text"> | </span><span class="post-meta-item-icon"><i class="fa fa-keyboard-o"></i><span class="post-count"> 8k</span><span class="post-meta-item-text"> 字</span></span></span><span class="post-time"> | <span class="post-meta-item-icon"><i class="fa fa-hourglass-half"></i><span class="post-count"> 40</span><span class="post-meta-item-text"> 分钟</span></span></span></div><a class="disqus-comment-count" href="/article/25164.html#vcomment"><span class="waline-comment-count" data-path="/article/25164.html"></span><span> 条评论</span></a><div class="post-content"><blockquote>
|
|
20
|
+
<p>作者:前端技匠 公号 / 罗学 (本文来自作者投稿)<br><code>原文地址:https://mp.weixin.qq.com/s/a0QFhcbwkhXStBfMOe7exA</code><br>很多人都或多或少使用过 webpack,但是很少有人能够系统的学习 webpack 配置,遇到错误的时候就会一脸懵,不知道从哪查起?性能优化时也不知道能做什么,网上的优化教程是不是符合自己的项目?等一系列问题!本文从最基础配置一步步到一个完善的大型项目的过程。让你对 webpack 再也不会畏惧,让它真正成为你的得力助手!</p>
|
|
21
|
+
</blockquote>
|
|
22
|
+
<span id="more"></span>
|
|
23
|
+
|
|
24
|
+
<p>本文从下面几个课题来实现</p>
|
|
25
|
+
<ul>
|
|
26
|
+
<li>课题 1:初探 webpack?探究 webpack 打包原理</li>
|
|
27
|
+
<li>课题 2:搭建开发环境跟生产环境</li>
|
|
28
|
+
<li>课题 3:基础配置之loader</li>
|
|
29
|
+
<li>课时 4:webpack性能优化</li>
|
|
30
|
+
<li>课时 5:手写loader实现可选链</li>
|
|
31
|
+
<li>课时 6:webpack编译优化</li>
|
|
32
|
+
<li>课时 7:多页面配置</li>
|
|
33
|
+
<li>课时 8:手写一个webpack插件</li>
|
|
34
|
+
<li>课时 9:构建 ssr</li>
|
|
35
|
+
</ul>
|
|
36
|
+
<h3 id="项目地址"><a href="#项目地址" class="headerlink" title="项目地址"></a>项目地址</h3><p><a target="_blank" rel="noopener" href="https://github.com/luoxue-victor/learn_webpack/">https://github.com/luoxue-victor/learn_webpack/</a></p>
|
|
37
|
+
<h3 id="脚手架"><a href="#脚手架" class="headerlink" title="脚手架"></a>脚手架</h3><pre><code>npm i -g webpack-box
|
|
38
|
+
</code></pre>
|
|
39
|
+
<h3 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h3><pre><code>webpack-box dev # 开发环境
|
|
40
|
+
webpack-box build # 生产环境
|
|
41
|
+
webpack-box dll # 编译差分包
|
|
42
|
+
webpack-box dev index # 指定页面编译(多页面)
|
|
43
|
+
webpack-box build index # 指定页面编译(多页面)
|
|
44
|
+
webpack-box build index --report # 开启打包分析
|
|
45
|
+
webpack-box build:ssr # 编译ssr
|
|
46
|
+
webpack-box ssr:server # 在 server 端运行
|
|
47
|
+
</code></pre>
|
|
48
|
+
<p>在 package.json 中使用</p>
|
|
49
|
+
<pre><code>{
|
|
50
|
+
"scripts": {
|
|
51
|
+
"dev": "webpack-box dev",
|
|
52
|
+
"build": "webpack-box build",
|
|
53
|
+
"dll": "webpack-box dll",
|
|
54
|
+
"build:ssr": "webpack-box build:ssr",
|
|
55
|
+
"ssr:server": "webpack-box ssr:server"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
</code></pre>
|
|
59
|
+
<p>使用</p>
|
|
60
|
+
<pre><code>npm run build --report # 开启打包分析
|
|
61
|
+
</code></pre>
|
|
62
|
+
<p>扩展配置</p>
|
|
63
|
+
<p>box.config.js</p>
|
|
64
|
+
<pre><code>module.exports = function (config) {
|
|
65
|
+
/**
|
|
66
|
+
* @param {object} dll 开启差分包
|
|
67
|
+
* @param {object} pages 多页面配置 通过 box run/build index 来使用
|
|
68
|
+
* @param {function} chainWebpack
|
|
69
|
+
* @param {string} entry 入口
|
|
70
|
+
* @param {string} output 出口
|
|
71
|
+
* @param {string} publicPath
|
|
72
|
+
* @param {string} port
|
|
73
|
+
*/
|
|
74
|
+
return {
|
|
75
|
+
entry: 'src/main.js',
|
|
76
|
+
output: 'dist',
|
|
77
|
+
publicPath: '/common/',
|
|
78
|
+
port: 8888,
|
|
79
|
+
dll: {
|
|
80
|
+
venders: ['vue', 'react']
|
|
81
|
+
},
|
|
82
|
+
pages: {
|
|
83
|
+
index: {
|
|
84
|
+
entry: 'src/main.js',
|
|
85
|
+
template: 'public/index.html',
|
|
86
|
+
filename: 'index.html',
|
|
87
|
+
},
|
|
88
|
+
index2: {
|
|
89
|
+
entry: 'src/main.js',
|
|
90
|
+
template: 'public/index2.html',
|
|
91
|
+
filename: 'index2.html',
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
chainWebpack(config) {
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
</code></pre>
|
|
99
|
+
<hr>
|
|
100
|
+
<h2 id="课题-1-初探-webpack?探究-webpack-打包原理"><a href="#课题-1-初探-webpack?探究-webpack-打包原理" class="headerlink" title="课题 1:初探 webpack?探究 webpack 打包原理"></a>课题 1:初探 webpack?探究 webpack 打包原理</h2><p>想要学好 <code>webpack</code>,我们首先要了解 <code>webpack</code> 的机制,我们先从js加载css开始学习。</p>
|
|
101
|
+
<p>我们从下面这个小练习开始走进 <code>webpack</code> 吧</p>
|
|
102
|
+
<p>在 <code>index.js</code> 中引入 <code>index.css</code></p>
|
|
103
|
+
<pre><code>const css = require('./index.css')
|
|
104
|
+
console.log(css)
|
|
105
|
+
</code></pre>
|
|
106
|
+
<p>css 文件并不能被 js 识别,webpack 也不例外,上述的写法不出意外会报错</p>
|
|
107
|
+
<p>我们如何让 webpack 识别 css 呢,答案就在 webpack 给我们提供了 loader 机制,可以让我们通过loader 将任意的文件转成 webpack 可以识别的文件</p>
|
|
108
|
+
<p>本章主要讲解</p>
|
|
109
|
+
<ol>
|
|
110
|
+
<li>webpack 基础配置</li>
|
|
111
|
+
<li>解析 bundle 如何加载模块</li>
|
|
112
|
+
<li>动态 import 加载原理</li>
|
|
113
|
+
<li>使用 webpack-chain 重写配置</li>
|
|
114
|
+
<li>课时 1 小结</li>
|
|
115
|
+
</ol>
|
|
116
|
+
<h3 id="webpack-基础配置"><a href="#webpack-基础配置" class="headerlink" title="webpack 基础配置"></a>webpack 基础配置</h3><h4 id="需要的依赖包"><a href="#需要的依赖包" class="headerlink" title="需要的依赖包"></a>需要的依赖包</h4><p>package.json</p>
|
|
117
|
+
<pre><code>{
|
|
118
|
+
"scripts": {
|
|
119
|
+
"dev": "cross-env NODE_ENV=development webpack", // 开发环境
|
|
120
|
+
"build": "cross-env NODE_ENV=production webpack" // 生产环境
|
|
121
|
+
},
|
|
122
|
+
"dependencies": {
|
|
123
|
+
"cross-env": "^6.0.3", // 兼容各种环境
|
|
124
|
+
"css-loader": "^3.2.0",
|
|
125
|
+
"rimraf": "^3.0.0", // 删除文件
|
|
126
|
+
"webpack": "^4.41.2"
|
|
127
|
+
},
|
|
128
|
+
"devDependencies": {
|
|
129
|
+
"webpack-cli": "^3.3.10"
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
</code></pre>
|
|
133
|
+
<h4 id="webpack-基础配置-1"><a href="#webpack-基础配置-1" class="headerlink" title="webpack 基础配置"></a>webpack 基础配置</h4><p>webpack.config.js</p>
|
|
134
|
+
<pre><code>const path = require('path');
|
|
135
|
+
const rimraf = require('rimraf');
|
|
136
|
+
|
|
137
|
+
// 删除 dist 目录
|
|
138
|
+
rimraf.sync('dist');
|
|
139
|
+
|
|
140
|
+
// webpack 配置
|
|
141
|
+
module.exports = {
|
|
142
|
+
entry: './src/index',
|
|
143
|
+
mode: process.env.NODE_ENV,
|
|
144
|
+
output: {
|
|
145
|
+
filename: 'bundle.js',
|
|
146
|
+
path: path.resolve(__dirname, 'dist')
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
</code></pre>
|
|
150
|
+
<h4 id="css-引入到-js"><a href="#css-引入到-js" class="headerlink" title="css 引入到 js"></a>css 引入到 js</h4><p>src/index.js</p>
|
|
151
|
+
<pre><code>const css = require('css-loader!./index.css');
|
|
152
|
+
const a = 100;
|
|
153
|
+
console.log(a, css);
|
|
154
|
+
</code></pre>
|
|
155
|
+
<h4 id="测试-css"><a href="#测试-css" class="headerlink" title="测试 css"></a>测试 css</h4><p>src/index.css</p>
|
|
156
|
+
<pre><code>body {
|
|
157
|
+
width: 100%;
|
|
158
|
+
height: 100vh;
|
|
159
|
+
background-color: orange;
|
|
160
|
+
}
|
|
161
|
+
</code></pre>
|
|
162
|
+
<h3 id="解析-bundle-如何加载模块"><a href="#解析-bundle-如何加载模块" class="headerlink" title="解析 bundle 如何加载模块"></a>解析 bundle 如何加载模块</h3><p>我删掉了一些注释跟一些干扰内容,这样看起来会更清晰一点</p>
|
|
163
|
+
<ul>
|
|
164
|
+
<li><code>bundle</code> 是一个立即执行函数,可以认为它是把所有模块捆绑在一起的一个巨型模块。</li>
|
|
165
|
+
<li><code>webpack</code> 将所有模块打包成了 <code>bundle</code> 的依赖,通过一个对象注入</li>
|
|
166
|
+
<li><code>0 模块</code> 就是入口</li>
|
|
167
|
+
<li><code>webpack</code> 通过 <code>__webpack_require__</code> 引入模块</li>
|
|
168
|
+
<li><code>__webpack_require__</code> 就是我们使用的 <code>require</code>,被 <code>webpack</code> 封装了一层</li>
|
|
169
|
+
</ul>
|
|
170
|
+
<p>dist/bundle.js</p>
|
|
171
|
+
<pre><code>(function(modules) {
|
|
172
|
+
function__webpack_require__(moduleId) {
|
|
173
|
+
if (installedModules[moduleId]) {
|
|
174
|
+
return installedModules[moduleId].exports;
|
|
175
|
+
}
|
|
176
|
+
varmodule = (installedModules[moduleId] = {
|
|
177
|
+
i: moduleId,
|
|
178
|
+
l: false,
|
|
179
|
+
exports: {}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
modules[moduleId].call(
|
|
183
|
+
module.exports,
|
|
184
|
+
module,
|
|
185
|
+
module.exports,
|
|
186
|
+
__webpack_require__
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
module.l = true;
|
|
190
|
+
|
|
191
|
+
returnmodule.exports;
|
|
192
|
+
}
|
|
193
|
+
return __webpack_require__((__webpack_require__.s = 0));
|
|
194
|
+
})({
|
|
195
|
+
'./src/index.js': function(module, exports, __webpack_require__) {
|
|
196
|
+
eval(`
|
|
197
|
+
const css = __webpack_require__("./src/style/index.css")
|
|
198
|
+
const a = 100;
|
|
199
|
+
console.log(a, css)
|
|
200
|
+
`);
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
'./src/style/index.css': function(module, exports, __webpack_require__) {
|
|
204
|
+
eval(`
|
|
205
|
+
exports = module.exports = __webpack_require__("./node_modules/css-loader/dist/runtime/api.js")(false);
|
|
206
|
+
exports.push([module.i, "body {
|
|
207
|
+
width: 100%;
|
|
208
|
+
height: 100vh;
|
|
209
|
+
background-color: orange;
|
|
210
|
+
}", ""]);
|
|
211
|
+
`);
|
|
212
|
+
},
|
|
213
|
+
|
|
214
|
+
0: function(module, exports, __webpack_require__) {
|
|
215
|
+
module.exports = __webpack_require__('./src/index.js');
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
</code></pre>
|
|
219
|
+
<h3 id="动态-import-加载原理"><a href="#动态-import-加载原理" class="headerlink" title="动态 import 加载原理"></a>动态 import 加载原理</h3><p>如果我们把 index.js 的 require 改成 import 会发生什么?</p>
|
|
220
|
+
<p>我们知道 <code>import</code> 跟 <code>require</code> 的区别是,<code>import</code> 是动态加载只有在用到的时候才会去加载,而 <code>require</code> 只要声明了就会加载,<code>webpack</code> 遇到了 <code>require</code> 就会把它当成一个模块加载到 <code>bundle</code> 的依赖里</p>
|
|
221
|
+
<p>那么问题来了,如果我们使用了 import 去引用一个模块,它是如何加载的呢?</p>
|
|
222
|
+
<h4 id="require-改成-import"><a href="#require-改成-import" class="headerlink" title="require 改成 import()"></a>require 改成 import()</h4><p>src/index.js</p>
|
|
223
|
+
<pre><code>// const css = require('css-loader!./index.css');
|
|
224
|
+
const css = import('css-loader!./index.css');
|
|
225
|
+
const a = 100;
|
|
226
|
+
console.log(a, css);
|
|
227
|
+
</code></pre>
|
|
228
|
+
<h4 id="动态加载打包结果"><a href="#动态加载打包结果" class="headerlink" title="动态加载打包结果"></a>动态加载打包结果</h4><p>除了正常的 <code>bundle</code> 之外,我们还可以看见一个 <code>0.boundle.js</code></p>
|
|
229
|
+
<p><code>0.boundle.js</code> 就是我们的动态加载的 <code>index.css</code> 模块</p>
|
|
230
|
+
<pre><code>|-- bundle.js
|
|
231
|
+
|-- 0.boundle.js
|
|
232
|
+
</code></pre>
|
|
233
|
+
<h4 id="动态模块"><a href="#动态模块" class="headerlink" title="动态模块"></a>动态模块</h4><p>0.boundle.js</p>
|
|
234
|
+
<p>这个文件就是把我们 <code>import</code> 的模块放进了一个单独的 <code>js</code> 文件中</p>
|
|
235
|
+
<pre><code>(window['webpackJsonp'] = window['webpackJsonp'] || []).push([
|
|
236
|
+
[0],
|
|
237
|
+
{
|
|
238
|
+
'./node_modules/css-loader/dist/runtime/api.js': function(
|
|
239
|
+
module,
|
|
240
|
+
exports,
|
|
241
|
+
__webpack_require__
|
|
242
|
+
) {
|
|
243
|
+
'use strict';
|
|
244
|
+
eval(`
|
|
245
|
+
...
|
|
246
|
+
`);
|
|
247
|
+
},
|
|
248
|
+
|
|
249
|
+
'./src/style/index.css': function(module, exports, __webpack_require__) {
|
|
250
|
+
eval(`
|
|
251
|
+
exports = module.exports = __webpack_require__("./node_modules/css-loader/dist/runtime/api.js")(false));
|
|
252
|
+
exports.push([module.i, \`body {
|
|
253
|
+
width: 100%;
|
|
254
|
+
height: 100vh;
|
|
255
|
+
background-color: orange;
|
|
256
|
+
},"\`]
|
|
257
|
+
`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
]);
|
|
261
|
+
</code></pre>
|
|
262
|
+
<h4 id="动态模块加载逻辑"><a href="#动态模块加载逻辑" class="headerlink" title="动态模块加载逻辑"></a>动态模块加载逻辑</h4><p>我们再看下 dist/bundle.js</p>
|
|
263
|
+
<p>方便理解,我把大部分代码和注释都删掉了</p>
|
|
264
|
+
<p>原理很简单,就是利用的 jsonp 的实现原理加载模块,只是在这里并不是从 server 拿数据而是从其他模块中</p>
|
|
265
|
+
<ol>
|
|
266
|
+
<li><p>调用模块时会在 <code>window</code> 上注册一个 <code>webpackJsonp</code> 数组,window[‘webpackJsonp’] = window[‘webpackJsonp’] || []</p>
|
|
267
|
+
</li>
|
|
268
|
+
<li><p>当我们 <code>import</code>时,<code>webpack</code> 会调用 <code>__webpack_require__.e(0)</code> 方法,也就是 <code>requireEnsure</code></p>
|
|
269
|
+
</li>
|
|
270
|
+
<li><p><code>webpack</code> 会动态创建一个 <code>script</code> 标签去加载这个模块,加载成功后会将该模块注入到 <code>webpackJsonp</code> 中</p>
|
|
271
|
+
</li>
|
|
272
|
+
<li><p><code>webpackJsonp.push</code> 会调用 <code>webpackJsonpCallback</code> 拿到模块</p>
|
|
273
|
+
</li>
|
|
274
|
+
<li><p>模块加载完(then)再使用 <code>__webpack_require__</code> 获取模块</p>
|
|
275
|
+
<p> (function(modules) {<br> functionwebpackJsonpCallback(data) {<br> var chunkIds = data[0];<br> var moreModules = data[1];<br> var moduleId,<br> chunkId,<br> i = 0,<br> resolves = [];<br> for (; i < chunkIds.length; i++) {<br> chunkId = chunkIds[i];<br> if (<br> Object.prototype.hasOwnProperty.call(installedChunks, chunkId) &&<br> installedChunks[chunkId]<br> ) {<br> resolves.push(installedChunks[chunkId][0]);<br> }<br> // 模块安装完<br> installedChunks[chunkId] = 0;<br> }<br> for (moduleId in moreModules) {<br> if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {<br> modules[moduleId] = moreModules[moduleId];<br> }<br> }<br> if (parentJsonpFunction) parentJsonpFunction(data);<br> while (resolves.length) {<br> // 执行所有 promise 的 resolve 函数<br> resolves.shift()();<br> }<br> }</p>
|
|
276
|
+
<p> functionjsonpScriptSrc(chunkId) {<br> return <strong>webpack_require</strong>.p + ‘’ + ({}[chunkId] || chunkId) + ‘.bundle.js’;<br> }</p>
|
|
277
|
+
<p> function__webpack_require__(moduleId) {<br> // …<br> }</p>
|
|
278
|
+
<p> <strong>webpack_require</strong>.e = functionrequireEnsure(chunkId) {<br> var promises = [];<br> // …<br> var script = document.createElement(‘script’);<br> var onScriptComplete;<br> script.charset = ‘utf-8’;<br> script.timeout = 120;<br> script.src = jsonpScriptSrc(chunkId);<br><br> onScriptComplete = function(event) {<br> // 处理异常,消除副作用<br> // …<br> };<br> var timeout = setTimeout(function() {<br> onScriptComplete({ type: ‘timeout’, target: script });<br> }, 120000);<br> script.onerror = script.onload = onScriptComplete;<br> document.head.appendChild(script);<br> // …<br> // 动态加载模块<br> returnPromise.all(promises);<br> };</p>
|
|
279
|
+
<p> var jsonpArray = (window[‘webpackJsonp’] = window[‘webpackJsonp’] || []);<br> // 重写数组 push 方法<br> jsonpArray.push = webpackJsonpCallback;<br> jsonpArray = jsonpArray.slice();<br> for (var i = 0; i < jsonpArray.length; i++)<br> webpackJsonpCallback(jsonpArray[i]);<br> return <strong>webpack_require</strong>((<strong>webpack_require</strong>.s = 0));<br> })({<br> ‘./src/index.js’: function(module, exports, <strong>webpack_require</strong>) {<br> eval(<code> const css = __webpack_require__.e(0).then(__webpack_require__.t.bind(null, "./src/style/index.css", 7)) const a = 100; console.log(a, css) </code>);<br> },<br> 0: function(module, exports, <strong>webpack_require</strong>) {<br> eval(<code>module.exports = __webpack_require__("./src/index.js");</code>);<br> }<br> });</p>
|
|
280
|
+
</li>
|
|
281
|
+
</ol>
|
|
282
|
+
<h3 id="使用-webpack-chain-重写配置"><a href="#使用-webpack-chain-重写配置" class="headerlink" title="使用 webpack-chain 重写配置"></a>使用 webpack-chain 重写配置</h3><p>我们用 webpack-chain 来写 webpack 的配置,原因是 webpack-chain 的方式更加灵活</p>
|
|
283
|
+
<p>官方解释</p>
|
|
284
|
+
<blockquote>
|
|
285
|
+
<p><code>webpack-chain</code> 尝试通过提供可链式或顺流式的 <code>API</code> 创建和修改 <code>webpack</code> 配置。<code>API</code> 的 <code>Key</code> 部分可以由用户指定的名称引用,这有助于跨项目修改配置方式的标准化。</p>
|
|
286
|
+
</blockquote>
|
|
287
|
+
<pre><code>const path = require('path');
|
|
288
|
+
const rimraf = require('rimraf');
|
|
289
|
+
const Config = require('webpack-chain');
|
|
290
|
+
const config = new Config();
|
|
291
|
+
const resolve = src => {
|
|
292
|
+
return path.join(process.cwd(), src);
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
// 删除 dist 目录
|
|
296
|
+
rimraf.sync('dist');
|
|
297
|
+
|
|
298
|
+
config
|
|
299
|
+
// 入口
|
|
300
|
+
.entry('src/index')
|
|
301
|
+
.add(resolve('src/index.js'))
|
|
302
|
+
.end()
|
|
303
|
+
// 模式
|
|
304
|
+
// .mode(process.env.NODE_ENV) 等价下面
|
|
305
|
+
.set('mode', process.env.NODE_ENV)
|
|
306
|
+
// 出口
|
|
307
|
+
.output.path(resolve('dist'))
|
|
308
|
+
.filename('[name].bundle.js');
|
|
309
|
+
|
|
310
|
+
config.module
|
|
311
|
+
.rule('css')
|
|
312
|
+
.test(/\.css$/)
|
|
313
|
+
.use('css')
|
|
314
|
+
.loader('css-loader');
|
|
315
|
+
|
|
316
|
+
module.exports = config.toConfig();
|
|
317
|
+
</code></pre>
|
|
318
|
+
<h3 id="课时-1-小结"><a href="#课时-1-小结" class="headerlink" title="课时 1 小结"></a>课时 1 小结</h3><p>至此课时 1 已经结束了,我们主要做了以下事情</p>
|
|
319
|
+
<ol>
|
|
320
|
+
<li>webpack 基础配置</li>
|
|
321
|
+
<li>将 css 通过 css-loader 打包进 js 中</li>
|
|
322
|
+
<li>解析 bundle 如何加载模块的</li>
|
|
323
|
+
<li>webpack 如何实现的动态加载模块</li>
|
|
324
|
+
</ol>
|
|
325
|
+
<p>学习一个工具我们不仅要看懂它的配置,还要对它的原理一起了解,只有学到框架的精髓,我们才能应对如今大前端如此迅猛的发展。</p>
|
|
326
|
+
<hr>
|
|
327
|
+
<h2 id="课题-2:搭建开发环境跟生产环境"><a href="#课题-2:搭建开发环境跟生产环境" class="headerlink" title="课题 2:搭建开发环境跟生产环境"></a>课题 2:搭建开发环境跟生产环境</h2><p>本章提要:</p>
|
|
328
|
+
<ul>
|
|
329
|
+
<li>目录</li>
|
|
330
|
+
<li>实现可插拔配置</li>
|
|
331
|
+
<li>构建生产环境</li>
|
|
332
|
+
<li>构建开发环境(devServer)</li>
|
|
333
|
+
<li>提取 css</li>
|
|
334
|
+
<li>自动生成 html</li>
|
|
335
|
+
<li>项目测试</li>
|
|
336
|
+
</ul>
|
|
337
|
+
<h3 id="目录"><a href="#目录" class="headerlink" title="目录"></a>目录</h3><pre><code>│── build
|
|
338
|
+
│ │── base.js // 公共部分
|
|
339
|
+
│ │── build.js
|
|
340
|
+
│ └── dev.js
|
|
341
|
+
│── config
|
|
342
|
+
│ │── base.js // 基础配置
|
|
343
|
+
│ │── css.js // css 配置
|
|
344
|
+
│ │── HtmlWebpackPlugin.js // html 配置
|
|
345
|
+
│ └── MiniCssExtractPlugin.js // 提取css
|
|
346
|
+
│── public // 公共资源
|
|
347
|
+
│ └── index.html // html 模版
|
|
348
|
+
└── src // 开发目录
|
|
349
|
+
│── style
|
|
350
|
+
│ └── index.css
|
|
351
|
+
└── main.js // 主入口
|
|
352
|
+
</code></pre>
|
|
353
|
+
<h3 id="实现可插拔配置"><a href="#实现可插拔配置" class="headerlink" title="实现可插拔配置"></a>实现可插拔配置</h3><p>package.json</p>
|
|
354
|
+
<pre><code>{
|
|
355
|
+
"scripts": {
|
|
356
|
+
"dev": "cross-env NODE_ENV=development node build/dev.js",
|
|
357
|
+
"build": "cross-env NODE_ENV=production node build/build.js"
|
|
358
|
+
},
|
|
359
|
+
"dependencies": {
|
|
360
|
+
"cross-env": "^6.0.3",
|
|
361
|
+
"css-loader": "^3.2.0",
|
|
362
|
+
"cssnano": "^4.1.10",
|
|
363
|
+
"ora": "^4.0.3",
|
|
364
|
+
"rimraf": "^3.0.0",
|
|
365
|
+
"webpack": "^4.41.2"
|
|
366
|
+
},
|
|
367
|
+
"devDependencies": {
|
|
368
|
+
"extract-text-webpack-plugin": "^3.0.2",
|
|
369
|
+
"html-webpack-plugin": "^3.2.0",
|
|
370
|
+
"mini-css-extract-plugin": "^0.8.0",
|
|
371
|
+
"vue-cli-plugin-commitlint": "^1.0.4",
|
|
372
|
+
"webpack-chain": "^6.0.0",
|
|
373
|
+
"webpack-cli": "^3.3.10",
|
|
374
|
+
"webpack-dev-server": "^3.9.0"
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
</code></pre>
|
|
378
|
+
<p>build/base.js</p>
|
|
379
|
+
<pre><code>const { findSync } = require('../lib');
|
|
380
|
+
const Config = require('webpack-chain');
|
|
381
|
+
const config = new Config();
|
|
382
|
+
const files = findSync('config');
|
|
383
|
+
const path = require('path');
|
|
384
|
+
const resolve = p => {
|
|
385
|
+
return path.join(process.cwd(), p);
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
module.exports = () => {
|
|
389
|
+
const map = newMap();
|
|
390
|
+
|
|
391
|
+
files.map(_ => {
|
|
392
|
+
const name = _.split('/')
|
|
393
|
+
.pop()
|
|
394
|
+
.replace('.js', '');
|
|
395
|
+
return map.set(name, require(_)(config, resolve));
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
map.forEach(v => v());
|
|
399
|
+
|
|
400
|
+
return config;
|
|
401
|
+
};
|
|
402
|
+
</code></pre>
|
|
403
|
+
<h3 id="构建生产环境"><a href="#构建生产环境" class="headerlink" title="构建生产环境"></a>构建生产环境</h3><p>build/build.js</p>
|
|
404
|
+
<pre><code>const rimraf = require('rimraf');
|
|
405
|
+
const ora = require('ora');
|
|
406
|
+
const chalk = require('chalk');
|
|
407
|
+
const path = require('path');
|
|
408
|
+
// 删除 dist 目录
|
|
409
|
+
rimraf.sync(path.join(process.cwd(), 'dist'));
|
|
410
|
+
|
|
411
|
+
const config = require('./base')();
|
|
412
|
+
const webpack = require('webpack');
|
|
413
|
+
const spinner = ora('开始构建项目...');
|
|
414
|
+
spinner.start();
|
|
415
|
+
|
|
416
|
+
webpack(config.toConfig(), function(err, stats) {
|
|
417
|
+
spinner.stop();
|
|
418
|
+
if (err) throw err;
|
|
419
|
+
process.stdout.write(
|
|
420
|
+
stats.toString({
|
|
421
|
+
colors: true,
|
|
422
|
+
modules: false,
|
|
423
|
+
children: false,
|
|
424
|
+
chunks: false,
|
|
425
|
+
chunkModules: false
|
|
426
|
+
}) + '\n\n'
|
|
427
|
+
);
|
|
428
|
+
|
|
429
|
+
if (stats.hasErrors()) {
|
|
430
|
+
console.log(chalk.red('构建失败\n'));
|
|
431
|
+
process.exit(1);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
console.log(chalk.cyan('build完成\n'));
|
|
435
|
+
});
|
|
436
|
+
</code></pre>
|
|
437
|
+
<h3 id="构建开发环境(devServer)"><a href="#构建开发环境(devServer)" class="headerlink" title="构建开发环境(devServer)"></a>构建开发环境(devServer)</h3><p>build/dev.js</p>
|
|
438
|
+
<pre><code>const config = require('./base')();
|
|
439
|
+
const webpack = require('webpack');
|
|
440
|
+
const chalk = require('chalk');
|
|
441
|
+
const WebpackDevServer = require('webpack-dev-server');
|
|
442
|
+
const port = 8080;
|
|
443
|
+
const publicPath = '/common/';
|
|
444
|
+
|
|
445
|
+
config.devServer
|
|
446
|
+
.quiet(true)
|
|
447
|
+
.hot(true)
|
|
448
|
+
.https(false)
|
|
449
|
+
.disableHostCheck(true)
|
|
450
|
+
.publicPath(publicPath)
|
|
451
|
+
.clientLogLevel('none');
|
|
452
|
+
|
|
453
|
+
const compiler = webpack(config.toConfig());
|
|
454
|
+
// 拿到 devServer 参数
|
|
455
|
+
const chainDevServer = compiler.options.devServer;
|
|
456
|
+
const server = new WebpackDevServer(
|
|
457
|
+
compiler,
|
|
458
|
+
Object.assign(chainDevServer, {})
|
|
459
|
+
);
|
|
460
|
+
|
|
461
|
+
['SIGINT', 'SIGTERM'].forEach(signal => {
|
|
462
|
+
process.on(signal, () => {
|
|
463
|
+
server.close(() => {
|
|
464
|
+
process.exit(0);
|
|
465
|
+
});
|
|
466
|
+
});
|
|
467
|
+
});
|
|
468
|
+
// 监听端口
|
|
469
|
+
server.listen(port);
|
|
470
|
+
|
|
471
|
+
newPromise(() => {
|
|
472
|
+
compiler.hooks.done.tap('dev', stats => {
|
|
473
|
+
const empty = ' ';
|
|
474
|
+
const common = `App running at:
|
|
475
|
+
- Local: http://127.0.0.1:${port}${publicPath}\n`;
|
|
476
|
+
console.log(chalk.cyan('\n' + empty + common));
|
|
477
|
+
});
|
|
478
|
+
});
|
|
479
|
+
</code></pre>
|
|
480
|
+
<h3 id="提取-css"><a href="#提取-css" class="headerlink" title="提取 css"></a>提取 css</h3><p>config/css.js</p>
|
|
481
|
+
<h4 id="css-提取-loader-配置"><a href="#css-提取-loader-配置" class="headerlink" title="css 提取 loader 配置"></a>css 提取 loader 配置</h4><pre><code>module.exports = (config, resolve) => {
|
|
482
|
+
return(lang, test) => {
|
|
483
|
+
const baseRule = config.module.rule(lang).test(test);
|
|
484
|
+
const normalRule = baseRule.oneOf('normal');
|
|
485
|
+
applyLoaders(normalRule);
|
|
486
|
+
functionapplyLoaders(rule) {
|
|
487
|
+
rule
|
|
488
|
+
.use('extract-css-loader')
|
|
489
|
+
.loader(require('mini-css-extract-plugin').loader)
|
|
490
|
+
.options({
|
|
491
|
+
publicPath: './'
|
|
492
|
+
});
|
|
493
|
+
rule
|
|
494
|
+
.use('css-loader')
|
|
495
|
+
.loader('css-loader')
|
|
496
|
+
.options({});
|
|
497
|
+
}
|
|
498
|
+
};
|
|
499
|
+
};
|
|
500
|
+
</code></pre>
|
|
501
|
+
<h4 id="css-提取插件-MiniCssExtractPlugin"><a href="#css-提取插件-MiniCssExtractPlugin" class="headerlink" title="css 提取插件 MiniCssExtractPlugin"></a>css 提取插件 MiniCssExtractPlugin</h4><p>config/MiniCssExtractPlugin.js</p>
|
|
502
|
+
<pre><code>const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
|
503
|
+
|
|
504
|
+
module.exports = (config, resolve) => {
|
|
505
|
+
return() => {
|
|
506
|
+
config
|
|
507
|
+
.oneOf('normal')
|
|
508
|
+
.plugin('mini-css-extract')
|
|
509
|
+
.use(MiniCssExtractPlugin);
|
|
510
|
+
};
|
|
511
|
+
};
|
|
512
|
+
</code></pre>
|
|
513
|
+
<h3 id="自动生成-html"><a href="#自动生成-html" class="headerlink" title="自动生成 html"></a>自动生成 html</h3><p>config/HtmlWebpackPlugin.js</p>
|
|
514
|
+
<pre><code>const HtmlWebpackPlugin = require('html-webpack-plugin');
|
|
515
|
+
|
|
516
|
+
module.exports = (config, resolve) => {
|
|
517
|
+
return() => {
|
|
518
|
+
config.plugin('html').use(HtmlWebpackPlugin, [
|
|
519
|
+
{
|
|
520
|
+
template: 'public/index.html'
|
|
521
|
+
}
|
|
522
|
+
]);
|
|
523
|
+
};
|
|
524
|
+
};
|
|
525
|
+
</code></pre>
|
|
526
|
+
<h3 id="项目测试"><a href="#项目测试" class="headerlink" title="项目测试"></a>项目测试</h3><h4 id="测试-html-模板"><a href="#测试-html-模板" class="headerlink" title="测试 html 模板"></a>测试 html 模板</h4><p>public/index.html</p>
|
|
527
|
+
<pre><code><!DOCTYPE html>
|
|
528
|
+
<html>
|
|
529
|
+
<head>
|
|
530
|
+
<metacharset="UTF-8">
|
|
531
|
+
<title>learn_webpack</title>
|
|
532
|
+
<body></body>
|
|
533
|
+
</html>
|
|
534
|
+
</code></pre>
|
|
535
|
+
<h4 id="测试-css-模板"><a href="#测试-css-模板" class="headerlink" title="测试 css 模板"></a>测试 css 模板</h4><p>src/style/index.css</p>
|
|
536
|
+
<pre><code>.test {
|
|
537
|
+
width: 200px;
|
|
538
|
+
height: 200px;
|
|
539
|
+
color: red;
|
|
540
|
+
background-color: orange;
|
|
541
|
+
}
|
|
542
|
+
</code></pre>
|
|
543
|
+
<h4 id="程序入口"><a href="#程序入口" class="headerlink" title="程序入口"></a>程序入口</h4><p>src/main.js</p>
|
|
544
|
+
<pre><code>require('./style/index.css');
|
|
545
|
+
|
|
546
|
+
const h2 = document.createElement('h2');
|
|
547
|
+
h2.className = 'test';
|
|
548
|
+
h2.innerText = 'test';
|
|
549
|
+
document.body.append(h2);
|
|
550
|
+
</code></pre>
|
|
551
|
+
<hr>
|
|
552
|
+
<h2 id="课题-3:基础配置之loader"><a href="#课题-3:基础配置之loader" class="headerlink" title="课题 3:基础配置之loader"></a>课题 3:基础配置之loader</h2><p>本章提要:</p>
|
|
553
|
+
<ul>
|
|
554
|
+
<li>配置 babel</li>
|
|
555
|
+
<li>使用 babel 配置 ts</li>
|
|
556
|
+
<li>ts 静态类型检查</li>
|
|
557
|
+
<li>友好错误提示插件</li>
|
|
558
|
+
<li>配置样式,style,css、less、sass、postcss 等</li>
|
|
559
|
+
<li>postcss 配置</li>
|
|
560
|
+
<li>编译前后 css 对比</li>
|
|
561
|
+
<li>配置 autoprefixer</li>
|
|
562
|
+
<li>开启 source map</li>
|
|
563
|
+
</ul>
|
|
564
|
+
<h3 id="目录-1"><a href="#目录-1" class="headerlink" title="目录"></a>目录</h3><p>增加以下文件</p>
|
|
565
|
+
<pre><code>│──── config // 配置目录
|
|
566
|
+
│ │── babelLoader.js // babel-loader 配置
|
|
567
|
+
│ │── ForkTsChecker.js // ts 静态检查
|
|
568
|
+
│ │── FriendlyErrorsWebpackPlugin.js // 友好错误提示
|
|
569
|
+
│ └── style
|
|
570
|
+
│──── src // 开发目录
|
|
571
|
+
│ │── style
|
|
572
|
+
│ │ │── app.css
|
|
573
|
+
│ │ │── index.less // 测试 less
|
|
574
|
+
│ │ │── index.scss // 测试 sass
|
|
575
|
+
│ │ └── index.postcss // 测试 postcss
|
|
576
|
+
│ └── ts
|
|
577
|
+
│ └── index.ts // 测试 ts
|
|
578
|
+
│── babel.js
|
|
579
|
+
│── postcss.config.js // postcss 配置
|
|
580
|
+
│── tsconfig.json // ts 配置
|
|
581
|
+
└──── dist // 打包后的目录
|
|
582
|
+
│── app.bundle.js
|
|
583
|
+
│── app.css
|
|
584
|
+
└── index.html
|
|
585
|
+
</code></pre>
|
|
586
|
+
<h3 id="配置-babel"><a href="#配置-babel" class="headerlink" title="配置 babel"></a>配置 babel</h3><p>config/babelLoader.js</p>
|
|
587
|
+
<pre><code>module.exports = (config, resolve) => {
|
|
588
|
+
const baseRule = config.module.rule('js').test(/.js│.tsx?$/);
|
|
589
|
+
const babelPath = resolve('babel.js');
|
|
590
|
+
const babelConf = require(babelPath);
|
|
591
|
+
const version = require(resolve('node_modules/@babel/core/package.json'))
|
|
592
|
+
.version;
|
|
593
|
+
return() => {
|
|
594
|
+
baseRule
|
|
595
|
+
.use('babel')
|
|
596
|
+
.loader(require.resolve('babel-loader'))
|
|
597
|
+
.options(babelConf({ version }));
|
|
598
|
+
};
|
|
599
|
+
};
|
|
600
|
+
</code></pre>
|
|
601
|
+
<h3 id="使用-babel-配置-ts"><a href="#使用-babel-配置-ts" class="headerlink" title="使用 babel 配置 ts"></a>使用 babel 配置 ts</h3><p>这里我们使用 <code>babel</code> 插件 <code>@babel/preset-typescript</code> 将 <code>ts</code> 转成 <code>js,并使用</code> <code>ForkTsCheckerWebpackPlugin</code>、<code>ForkTsCheckerNotifierWebpackPlugin</code> 插件进行错误提示。</p>
|
|
602
|
+
<p>babel.js</p>
|
|
603
|
+
<pre><code>module.exports = function(api) {
|
|
604
|
+
return {
|
|
605
|
+
presets: [
|
|
606
|
+
[
|
|
607
|
+
'@babel/preset-env',
|
|
608
|
+
{
|
|
609
|
+
targets: {
|
|
610
|
+
chrome: 59,
|
|
611
|
+
edge: 13,
|
|
612
|
+
firefox: 50,
|
|
613
|
+
safari: 8
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
],
|
|
617
|
+
[
|
|
618
|
+
'@babel/preset-typescript',
|
|
619
|
+
{
|
|
620
|
+
allExtensions: true
|
|
621
|
+
}
|
|
622
|
+
]
|
|
623
|
+
],
|
|
624
|
+
plugins: [
|
|
625
|
+
'@babel/plugin-transform-typescript',
|
|
626
|
+
'transform-class-properties',
|
|
627
|
+
'@babel/proposal-object-rest-spread'
|
|
628
|
+
]
|
|
629
|
+
};
|
|
630
|
+
};
|
|
631
|
+
</code></pre>
|
|
632
|
+
<h3 id="ts-静态类型检查"><a href="#ts-静态类型检查" class="headerlink" title="ts 静态类型检查"></a>ts 静态类型检查</h3><pre><code>const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
|
|
633
|
+
const ForkTsCheckerNotifierWebpackPlugin = require('fork-ts-checker-notifier-webpack-plugin');
|
|
634
|
+
|
|
635
|
+
module.exports = (config, resolve) => {
|
|
636
|
+
return() => {
|
|
637
|
+
config.plugin('ts-fork').use(ForkTsCheckerWebpackPlugin, [
|
|
638
|
+
{
|
|
639
|
+
// 将async设为false,可以阻止Webpack的emit以等待类型检查器/linter,并向Webpack的编译添加错误。
|
|
640
|
+
async: false
|
|
641
|
+
}
|
|
642
|
+
]);
|
|
643
|
+
// 将TypeScript类型检查错误以弹框提示
|
|
644
|
+
// 如果fork-ts-checker-webpack-plugin的async为false时可以不用
|
|
645
|
+
// 否则建议使用,以方便发现错误
|
|
646
|
+
config.plugin('ts-notifier').use(ForkTsCheckerNotifierWebpackPlugin, [
|
|
647
|
+
{
|
|
648
|
+
title: 'TypeScript',
|
|
649
|
+
excludeWarnings: true,
|
|
650
|
+
skipSuccessful: true
|
|
651
|
+
}
|
|
652
|
+
]);
|
|
653
|
+
};
|
|
654
|
+
};
|
|
655
|
+
</code></pre>
|
|
656
|
+
<h3 id="友好错误提示插件"><a href="#友好错误提示插件" class="headerlink" title="友好错误提示插件"></a>友好错误提示插件</h3><p>config/FriendlyErrorsWebpackPlugin.js</p>
|
|
657
|
+
<pre><code>const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin');
|
|
658
|
+
|
|
659
|
+
module.exports = (config, resolve) => {
|
|
660
|
+
return() => {
|
|
661
|
+
config.plugin('error').use(FriendlyErrorsWebpackPlugin);
|
|
662
|
+
};
|
|
663
|
+
};
|
|
664
|
+
</code></pre>
|
|
665
|
+
<h3 id="配置样式,style,css、less、sass、postcss-等"><a href="#配置样式,style,css、less、sass、postcss-等" class="headerlink" title="配置样式,style,css、less、sass、postcss 等"></a>配置样式,style,css、less、sass、postcss 等</h3><pre><code>module.exports = (config, resolve) => {
|
|
666
|
+
const createCSSRule = (lang, test, loader, options = {}) => {
|
|
667
|
+
const baseRule = config.module.rule(lang).test(test);
|
|
668
|
+
const normalRule = baseRule.oneOf('normal');
|
|
669
|
+
normalRule
|
|
670
|
+
.use('extract-css-loader')
|
|
671
|
+
.loader(require('mini-css-extract-plugin').loader)
|
|
672
|
+
.options({
|
|
673
|
+
hmr: process.env.NODE_ENV === 'development',
|
|
674
|
+
publicPath: '/'
|
|
675
|
+
});
|
|
676
|
+
normalRule
|
|
677
|
+
.use('css-loader')
|
|
678
|
+
.loader(require.resolve('css-loader'))
|
|
679
|
+
.options({});
|
|
680
|
+
normalRule.use('postcss-loader').loader(require.resolve('postcss-loader'));
|
|
681
|
+
if (loader) {
|
|
682
|
+
const rs = require.resolve(loader);
|
|
683
|
+
normalRule
|
|
684
|
+
.use(loader)
|
|
685
|
+
.loader(rs)
|
|
686
|
+
.options(options);
|
|
687
|
+
}
|
|
688
|
+
};
|
|
689
|
+
|
|
690
|
+
return() => {
|
|
691
|
+
createCSSRule('css', /\.css$/, 'css-loader', {});
|
|
692
|
+
createCSSRule('less', /\.less$/, 'less-loader', {});
|
|
693
|
+
createCSSRule('scss', /\.scss$/, 'sass-loader', {});
|
|
694
|
+
createCSSRule('postcss', /\.p(ost)?css$/);
|
|
695
|
+
};
|
|
696
|
+
};
|
|
697
|
+
</code></pre>
|
|
698
|
+
<h3 id="postcss-配置"><a href="#postcss-配置" class="headerlink" title="postcss 配置"></a>postcss 配置</h3><pre><code>module.exports = {
|
|
699
|
+
plugins: {
|
|
700
|
+
'postcss-px-to-viewport': {
|
|
701
|
+
unitToConvert: 'px',
|
|
702
|
+
viewportWidth: 750,
|
|
703
|
+
unitPrecision: 5,
|
|
704
|
+
propList: ['*'],
|
|
705
|
+
viewportUnit: 'vw',
|
|
706
|
+
fontViewportUnit: 'vw',
|
|
707
|
+
selectorBlackList: [],
|
|
708
|
+
minPixelValue: 1,
|
|
709
|
+
mediaQuery: false,
|
|
710
|
+
replace: true,
|
|
711
|
+
exclude: [],
|
|
712
|
+
landscape: false,
|
|
713
|
+
landscapeUnit: 'vw',
|
|
714
|
+
landscapeWidth: 568
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
};
|
|
718
|
+
</code></pre>
|
|
719
|
+
<h3 id="编译前后-css-对比"><a href="#编译前后-css-对比" class="headerlink" title="编译前后 css 对比"></a>编译前后 css 对比</h3><p>src/style/index.less</p>
|
|
720
|
+
<pre><code>/* index.less */
|
|
721
|
+
.test {
|
|
722
|
+
width: 300px;
|
|
723
|
+
}
|
|
724
|
+
</code></pre>
|
|
725
|
+
<p>dist/app.css</p>
|
|
726
|
+
<pre><code>/* index.css */
|
|
727
|
+
.test {
|
|
728
|
+
width: 36.66667vw;
|
|
729
|
+
height: 26.66667vw;
|
|
730
|
+
color: red;
|
|
731
|
+
background-color: orange;
|
|
732
|
+
}
|
|
733
|
+
/* app.css */
|
|
734
|
+
.test {
|
|
735
|
+
font-size: 8vw;
|
|
736
|
+
}
|
|
737
|
+
/* index.less */
|
|
738
|
+
.test {
|
|
739
|
+
width: 40vw;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
/* index.scss */
|
|
743
|
+
.test {
|
|
744
|
+
height: 40vw;
|
|
745
|
+
}
|
|
746
|
+
/* index.postcss */
|
|
747
|
+
.test {
|
|
748
|
+
background: green;
|
|
749
|
+
height: 26.66667vw;
|
|
750
|
+
}
|
|
751
|
+
</code></pre>
|
|
752
|
+
<h3 id="配置-autoprefixer"><a href="#配置-autoprefixer" class="headerlink" title="配置 autoprefixer"></a>配置 autoprefixer</h3><p>自动添加 css 前缀</p>
|
|
753
|
+
<p>postcss.config.js</p>
|
|
754
|
+
<pre><code>module.exports = {
|
|
755
|
+
plugins: {
|
|
756
|
+
autoprefixer: {
|
|
757
|
+
overrideBrowserslist: [
|
|
758
|
+
'> 1%',
|
|
759
|
+
'last 3 versions',
|
|
760
|
+
'iOS >= 8',
|
|
761
|
+
'Android >= 4',
|
|
762
|
+
'Chrome >= 40'
|
|
763
|
+
]
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
};
|
|
767
|
+
</code></pre>
|
|
768
|
+
<h4 id="转换前"><a href="#转换前" class="headerlink" title="转换前"></a>转换前</h4><pre><code>/* index.css */
|
|
769
|
+
.test {
|
|
770
|
+
width: 200px;
|
|
771
|
+
height: 200px;
|
|
772
|
+
color: red;
|
|
773
|
+
display: flex;
|
|
774
|
+
background-color: orange;
|
|
775
|
+
}
|
|
776
|
+
</code></pre>
|
|
777
|
+
<h4 id="转换后"><a href="#转换后" class="headerlink" title="转换后"></a>转换后</h4><pre><code>/* index.css */
|
|
778
|
+
.test {
|
|
779
|
+
width: 26.66667vw;
|
|
780
|
+
height: 26.66667vw;
|
|
781
|
+
color: red;
|
|
782
|
+
display: -webkit-box;
|
|
783
|
+
display: -webkit-flex;
|
|
784
|
+
display: -ms-flexbox;
|
|
785
|
+
display: flex;
|
|
786
|
+
background-color: orange;
|
|
787
|
+
}
|
|
788
|
+
</code></pre>
|
|
789
|
+
<h3 id="开启-source-map"><a href="#开启-source-map" class="headerlink" title="开启 source map"></a>开启 source map</h3><pre><code>config.devtool('cheap-source-map');
|
|
790
|
+
|
|
791
|
+
|
|
792
|
+
└── dist
|
|
793
|
+
│── app.bundle.js
|
|
794
|
+
│── app.bundle.js.map
|
|
795
|
+
│── app.css
|
|
796
|
+
│── app.css.map
|
|
797
|
+
└── index.html
|
|
798
|
+
</code></pre>
|
|
799
|
+
<p>在源文件下会有一行注释,证明开启了 sourcemap</p>
|
|
800
|
+
<pre><code>/*# sourceMappingURL=app.css.map*/
|
|
801
|
+
</code></pre>
|
|
802
|
+
<hr>
|
|
803
|
+
<h2 id="课时-4:webpack性能优化"><a href="#课时-4:webpack性能优化" class="headerlink" title="课时 4:webpack性能优化"></a>课时 4:webpack性能优化</h2><p>本章讲解</p>
|
|
804
|
+
<ol>
|
|
805
|
+
<li>分离 Manifest</li>
|
|
806
|
+
<li>Code Splitting(代码分割)</li>
|
|
807
|
+
<li>Bundle Splitting(打包分割)</li>
|
|
808
|
+
<li>Tree Shaking(删除死代码)</li>
|
|
809
|
+
<li>开启 gzip</li>
|
|
810
|
+
</ol>
|
|
811
|
+
<h3 id="分离-Manifest"><a href="#分离-Manifest" class="headerlink" title="分离 Manifest"></a>分离 Manifest</h3><pre><code>module.exports = (config, resolve) => {
|
|
812
|
+
return() => {
|
|
813
|
+
config
|
|
814
|
+
.optimization
|
|
815
|
+
.runtimeChunk({
|
|
816
|
+
name: "manifest"
|
|
817
|
+
})
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
</code></pre>
|
|
821
|
+
<h3 id="Code-Splitting"><a href="#Code-Splitting" class="headerlink" title="Code Splitting"></a>Code Splitting</h3><ol>
|
|
822
|
+
<li>使用动态 import 或者 require.ensure 语法,在第一节已经讲解</li>
|
|
823
|
+
<li>使用 <code>babel-plugin-import</code> 插件按需引入一些组件库</li>
|
|
824
|
+
</ol>
|
|
825
|
+
<h3 id="Bundle-Splitting"><a href="#Bundle-Splitting" class="headerlink" title="Bundle Splitting"></a>Bundle Splitting</h3><p>将公共的包提取到 <code>chunk-vendors</code> 里面,比如你require(‘vue’),webpack 会将 vue 打包进 chunk-vendors.bundle.js</p>
|
|
826
|
+
<pre><code>module.exports = (config, resolve) => {
|
|
827
|
+
return() => {
|
|
828
|
+
config
|
|
829
|
+
.optimization.splitChunks({
|
|
830
|
+
chunks: 'async',
|
|
831
|
+
minSize: 30000,
|
|
832
|
+
minChunks: 1,
|
|
833
|
+
maxAsyncRequests: 3,
|
|
834
|
+
maxInitialRequests: 3,
|
|
835
|
+
cacheGroups: {
|
|
836
|
+
vendors: {
|
|
837
|
+
name: `chunk-vendors`,
|
|
838
|
+
test: /[\\/]node_modules[\\/]/,
|
|
839
|
+
priority: -10,
|
|
840
|
+
chunks: 'initial'
|
|
841
|
+
},
|
|
842
|
+
common: {
|
|
843
|
+
name: `chunk-common`,
|
|
844
|
+
minChunks: 2,
|
|
845
|
+
priority: -20,
|
|
846
|
+
chunks: 'initial',
|
|
847
|
+
reuseExistingChunk: true
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
})
|
|
851
|
+
config.optimization.usedExports(true)
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
</code></pre>
|
|
855
|
+
<h3 id="Tree-Shaking"><a href="#Tree-Shaking" class="headerlink" title="Tree Shaking"></a>Tree Shaking</h3><p>config/optimization.js</p>
|
|
856
|
+
<pre><code>config.optimization.usedExports(true);
|
|
857
|
+
</code></pre>
|
|
858
|
+
<p>src/treeShaking.js</p>
|
|
859
|
+
<pre><code>exportfunctionsquare(x) {
|
|
860
|
+
return x * x;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
exportfunctioncube(x) {
|
|
864
|
+
return x * x * x;
|
|
865
|
+
}
|
|
866
|
+
</code></pre>
|
|
867
|
+
<p>在 main.js 中只引用了 cube</p>
|
|
868
|
+
<pre><code>import { cube } from'./treeShaking';
|
|
869
|
+
|
|
870
|
+
console.log(cube(2));
|
|
871
|
+
</code></pre>
|
|
872
|
+
<h4 id="未使用-Tree-Shaking"><a href="#未使用-Tree-Shaking" class="headerlink" title="未使用 Tree Shaking"></a>未使用 Tree Shaking</h4><pre><code>{
|
|
873
|
+
"./src/treeShaking.js": function(
|
|
874
|
+
module,
|
|
875
|
+
__webpack_exports__,
|
|
876
|
+
__webpack_require__
|
|
877
|
+
) {
|
|
878
|
+
"use strict";
|
|
879
|
+
__webpack_require__.r(__webpack_exports__);
|
|
880
|
+
__webpack_require__.d(__webpack_exports__, "square", function() {
|
|
881
|
+
return square;
|
|
882
|
+
});
|
|
883
|
+
__webpack_require__.d(__webpack_exports__, "cube", function() {
|
|
884
|
+
return cube;
|
|
885
|
+
});
|
|
886
|
+
functionsquare(x) {
|
|
887
|
+
return x * x;
|
|
888
|
+
}
|
|
889
|
+
functioncube(x) {
|
|
890
|
+
return x * x * x;
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
</code></pre>
|
|
895
|
+
<h4 id="使用了-Tree-Shaking"><a href="#使用了-Tree-Shaking" class="headerlink" title="使用了 Tree Shaking"></a>使用了 Tree Shaking</h4><p>这里只导出了 cube 函数,并没有将 square 导出去</p>
|
|
896
|
+
<p>当然你可以看见 square 函数还是在 bundle 里面,但是在压缩的时候就会被干掉了,因为它并没有被引用</p>
|
|
897
|
+
<pre><code>{
|
|
898
|
+
"./src/treeShaking.js": function(
|
|
899
|
+
module,
|
|
900
|
+
__webpack_exports__,
|
|
901
|
+
__webpack_require__
|
|
902
|
+
) {
|
|
903
|
+
"use strict";
|
|
904
|
+
__webpack_require__.d(__webpack_exports__, "a", function() {
|
|
905
|
+
return cube;
|
|
906
|
+
});
|
|
907
|
+
functionsquare(x) {
|
|
908
|
+
return x * x;
|
|
909
|
+
}
|
|
910
|
+
functioncube(x) {
|
|
911
|
+
return x * x * x;
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
</code></pre>
|
|
916
|
+
<p>只有当函数给定输入后,产生相应的输出,且不修改任何外部的东西,才可以安全做shaking的操作</p>
|
|
917
|
+
<p>如何使用tree-shaking?</p>
|
|
918
|
+
<ol>
|
|
919
|
+
<li>确保代码是es6格式,即 export,import</li>
|
|
920
|
+
<li>package.json中,设置 sideEffects</li>
|
|
921
|
+
<li>确保 tree-shaking 的函数没有副作用</li>
|
|
922
|
+
<li>babelrc中设置presets [[“@babel/preset-env”, { “modules”: false }]] 禁止转换模块,交由webpack进行模块化处理</li>
|
|
923
|
+
<li>结合uglifyjs-webpack-plugin</li>
|
|
924
|
+
</ol>
|
|
925
|
+
<p>其实在 <code>webpack4</code> 我们根本不需要做这些操作了,因为 <code>webpack</code> 在生产环境已经帮我们默认添加好了,开箱即用!</p>
|
|
926
|
+
<h3 id="开启-gzip"><a href="#开启-gzip" class="headerlink" title="开启 gzip"></a>开启 gzip</h3><p>CompressionWebpackPlugin.js</p>
|
|
927
|
+
<pre><code>const CompressionWebpackPlugin = require('compression-webpack-plugin');
|
|
928
|
+
|
|
929
|
+
module.exports = (config, resolve) => {
|
|
930
|
+
return() => {
|
|
931
|
+
config.plugin('CompressionWebpackPlugin').use(CompressionWebpackPlugin, [
|
|
932
|
+
{
|
|
933
|
+
algorithm: 'gzip',
|
|
934
|
+
test: /\.js(\?.*)?$/i,
|
|
935
|
+
threshold: 10240,
|
|
936
|
+
minRatio: 0.8
|
|
937
|
+
}
|
|
938
|
+
]);
|
|
939
|
+
};
|
|
940
|
+
};
|
|
941
|
+
</code></pre>
|
|
942
|
+
<hr>
|
|
943
|
+
<h2 id="课时-5:手写loader实现可选链"><a href="#课时-5:手写loader实现可选链" class="headerlink" title="课时 5:手写loader实现可选链"></a>课时 5:手写loader实现可选链</h2><p>本章内容</p>
|
|
944
|
+
<ol>
|
|
945
|
+
<li>什么是 webpack loader</li>
|
|
946
|
+
<li>可选链介绍</li>
|
|
947
|
+
<li>loader 实现可选链</li>
|
|
948
|
+
</ol>
|
|
949
|
+
<h3 id="什么是-webpack-loader"><a href="#什么是-webpack-loader" class="headerlink" title="什么是 webpack loader"></a>什么是 webpack loader</h3><p><code>webpack loader</code> 是 <code>webpack</code> 为了处理各种类型文件的一个中间层,<code>webpack</code> 本质上就是一个 <code>node</code> 模块,它不能处理 <code>js</code> 以外的文件,那么 <code>loader</code> 就帮助 <code>webpack</code> 做了一层转换,将所有文件都转成字符串,你可以对字符串进行任意操作/修改,然后返回给 <code>webpack</code> 一个包含这个字符串的对象,让 <code>webpack</code> 进行后面的处理。如果把 <code>webpack</code> 当成一个垃圾工厂的话,那么 <code>loader</code> 就是这个工厂的垃圾分类!</p>
|
|
950
|
+
<h3 id="可选链介绍"><a href="#可选链介绍" class="headerlink" title="可选链介绍"></a>可选链介绍</h3><p>这里并不是纯粹意义上的可选链,因为 <code>babel</code> 跟 <code>ts</code> 都已经支持了,我们也没有必要去写一个完整的可选链,只是来加深一下对 <code>loader</code> 的理解, <code>loader</code> 在工作当中能帮助我们做什么?</p>
|
|
951
|
+
<p><strong><code>用途</code></strong> 当我们访问一个对象属性时不必担心这个对象是 <code>undefined</code> 而报错,导致程序不能继续向下执行</p>
|
|
952
|
+
<p><strong><code>解释</code></strong> 在 <code>?</code> 之前的所有访问链路都是合法的,不会产生报错</p>
|
|
953
|
+
<pre><code>const obj = {
|
|
954
|
+
foo: {
|
|
955
|
+
bar: {
|
|
956
|
+
baz: 2
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
console.log(obj.foo.bar?.baz) // 2
|
|
962
|
+
// 被转成 obj && obj.foo && obj.foo.bar && obj.foo.bar.baz
|
|
963
|
+
console.log(obj.foo.err?.baz) // undefined
|
|
964
|
+
// 被转成 obj && obj.foo && obj.foo.err && obj.foo.err.baz
|
|
965
|
+
</code></pre>
|
|
966
|
+
<h3 id="loader-实现可选链"><a href="#loader-实现可选链" class="headerlink" title="loader 实现可选链"></a>loader 实现可选链</h3><p>配置loader,options-chain-loader</p>
|
|
967
|
+
<p>config/OptionsChainLoader.js</p>
|
|
968
|
+
<pre><code>module.exports = (config, resolve) => {
|
|
969
|
+
const baseRule = config.module.rule('js').test(/.js|.tsx?$/);
|
|
970
|
+
const normalRule = baseRule.oneOf('normal');
|
|
971
|
+
return() => {
|
|
972
|
+
normalRule
|
|
973
|
+
.use('options-chain')
|
|
974
|
+
.loader(resolve('options-chain-loader'))
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
</code></pre>
|
|
978
|
+
<p>其实就是正则替换,<code>loader</code> 将整个文件全部转换成字符串,<code>content</code> 就是整个文件的内容,对 <code>content</code> 进行修改,修改完成后再返回一个新的 <code>content</code> 就完成了一个 <code>loader</code> 转换。是不是很简单?</p>
|
|
979
|
+
<p>下面的操作意思就是,我们匹配 <code>obj.foo.bar?.</code> 并把它转成 <code>obj && obj.foo && obj.foo.bar && obj.foo.bar.</code></p>
|
|
980
|
+
<p>options-chain-loader.js</p>
|
|
981
|
+
<pre><code>module.exports = function(content) {
|
|
982
|
+
return content.replace(newRegExp(/([\$_\w\.]+\?\.)/,'g'),function(res) {
|
|
983
|
+
let str = res.replace(/\?\./,'');
|
|
984
|
+
let arrs = str.split('.');
|
|
985
|
+
let strArr = [];
|
|
986
|
+
for(let i = 1; i <= arrs.length; i++) {
|
|
987
|
+
strArr.push(arrs.slice(0,i).join('.'));
|
|
988
|
+
}
|
|
989
|
+
let compile = strArr.join('&&');
|
|
990
|
+
const done = compile + '&&' + str + '.'
|
|
991
|
+
return done;
|
|
992
|
+
});
|
|
993
|
+
};
|
|
994
|
+
</code></pre>
|
|
995
|
+
<hr>
|
|
996
|
+
<h2 id="课时-6:webpack编译优化"><a href="#课时-6:webpack编译优化" class="headerlink" title="课时 6:webpack编译优化"></a>课时 6:webpack编译优化</h2><p>本章内容</p>
|
|
997
|
+
<ol>
|
|
998
|
+
<li>cache-loader</li>
|
|
999
|
+
<li>DllPlugin</li>
|
|
1000
|
+
<li>threadLoader</li>
|
|
1001
|
+
</ol>
|
|
1002
|
+
<h3 id="cache-loader"><a href="#cache-loader" class="headerlink" title="cache-loader"></a>cache-loader</h3><p><code>cache-loader</code> 主要是将打包好的文件缓存在硬盘的一个目录里,一般存在 <code>node_modules/.cache</code> 下,当你再次 <code>build</code> 的时候如果此文件没有修改就会从缓存中读取已经编译过的文件,只有有改动的才会被编译,这样就大大降低了编译的时间。尤其是项目越大时越明显。</p>
|
|
1003
|
+
<p>此项目使用前后数据对比 3342ms –> 2432ms 效果还是比较明显</p>
|
|
1004
|
+
<p>这里只对 babel 加入了 cache-loader,因为我们的 ts/js 都是由 babel 进行编译的,不需要对 ts-loader 缓存(我们也没有用到)</p>
|
|
1005
|
+
<p>config/cacheLoader.js</p>
|
|
1006
|
+
<pre><code>module.exports = (config, resolve) => {
|
|
1007
|
+
const baseRule = config.module.rule('js').test(/.js|.tsx?$/);
|
|
1008
|
+
const babelPath = resolve('babel.js')
|
|
1009
|
+
const babelConf = require(babelPath);
|
|
1010
|
+
const version = require(resolve('node_modules/@babel/core/package.json')).version
|
|
1011
|
+
return() => {
|
|
1012
|
+
baseRule
|
|
1013
|
+
.exclude
|
|
1014
|
+
.add(filepath => {
|
|
1015
|
+
// 不缓存 node_modules 下的文件
|
|
1016
|
+
return/node_modules/.test(filepath)
|
|
1017
|
+
})
|
|
1018
|
+
.end()
|
|
1019
|
+
.use('cache-loader')
|
|
1020
|
+
.loader('cache-loader')
|
|
1021
|
+
.options({
|
|
1022
|
+
// 缓存位置
|
|
1023
|
+
cacheDirectory: resolve('node_modules/.cache/babel')
|
|
1024
|
+
})
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
</code></pre>
|
|
1028
|
+
<h3 id="DllPlugin"><a href="#DllPlugin" class="headerlink" title="DllPlugin"></a>DllPlugin</h3><p>DllPlugin 是将第三方长期不变的包与实际项目隔离开来并分别打包,当我们 build 时再将已经打包好的 dll 包引进来就 ok 了</p>
|
|
1029
|
+
<p>我提取了两个包 vue、react,速度差不多提升了 200ms,从 2698ms 到 2377ms</p>
|
|
1030
|
+
<h4 id="打包-dll"><a href="#打包-dll" class="headerlink" title="打包 dll"></a>打包 dll</h4><p>build/dll.js</p>
|
|
1031
|
+
<pre><code>const path = require("path");
|
|
1032
|
+
const dllPath = path.join(process.cwd(), 'dll');
|
|
1033
|
+
const Config = require('webpack-chain');
|
|
1034
|
+
const config = new Config();
|
|
1035
|
+
const webpack = require('webpack')
|
|
1036
|
+
const rimraf = require('rimraf');
|
|
1037
|
+
const ora = require('ora')
|
|
1038
|
+
const chalk = require('chalk')
|
|
1039
|
+
const BundleAnalyzerPlugin = require('../config/BundleAnalyzerPlugin')(config)
|
|
1040
|
+
|
|
1041
|
+
BundleAnalyzerPlugin()
|
|
1042
|
+
config
|
|
1043
|
+
.entry('dll')
|
|
1044
|
+
.add('vue')
|
|
1045
|
+
.add('react')
|
|
1046
|
+
.end()
|
|
1047
|
+
.set('mode', "production")
|
|
1048
|
+
.output
|
|
1049
|
+
.path(dllPath)
|
|
1050
|
+
.filename('[name].js')
|
|
1051
|
+
.library("[name]")
|
|
1052
|
+
.end()
|
|
1053
|
+
.plugin('DllPlugin')
|
|
1054
|
+
.use(webpack.DllPlugin, [{
|
|
1055
|
+
name: "[name]",
|
|
1056
|
+
path: path.join(process.cwd(), 'dll', 'manifest.json'),
|
|
1057
|
+
}])
|
|
1058
|
+
.end()
|
|
1059
|
+
|
|
1060
|
+
rimraf.sync(path.join(process.cwd(), 'dll'))
|
|
1061
|
+
const spinner = ora('开始构建项目...')
|
|
1062
|
+
spinner.start()
|
|
1063
|
+
|
|
1064
|
+
webpack(config.toConfig(), function (err, stats) {
|
|
1065
|
+
spinner.stop()
|
|
1066
|
+
if (err) throw err
|
|
1067
|
+
process.stdout.write(stats.toString({
|
|
1068
|
+
colors: true,
|
|
1069
|
+
modules: false,
|
|
1070
|
+
children: false,
|
|
1071
|
+
chunks: false,
|
|
1072
|
+
chunkModules: false
|
|
1073
|
+
}) + '\n\n')
|
|
1074
|
+
|
|
1075
|
+
if (stats.hasErrors()) {
|
|
1076
|
+
console.log(chalk.red('构建失败\n'))
|
|
1077
|
+
process.exit(1)
|
|
1078
|
+
}
|
|
1079
|
+
console.log(chalk.cyan('build完成\n'))
|
|
1080
|
+
})
|
|
1081
|
+
</code></pre>
|
|
1082
|
+
<h3 id="将-dll-包合并"><a href="#将-dll-包合并" class="headerlink" title="将 dll 包合并"></a>将 dll 包合并</h3><pre><code>const webpack = require('webpack')
|
|
1083
|
+
|
|
1084
|
+
module.exports = (config, resolve) => {
|
|
1085
|
+
return() => {
|
|
1086
|
+
config.plugin('DllPlugin')
|
|
1087
|
+
.use(webpack.DllReferencePlugin, [{
|
|
1088
|
+
context: process.cwd(),
|
|
1089
|
+
manifest: require(resolve('dll/manifest.json'))
|
|
1090
|
+
}])
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
</code></pre>
|
|
1094
|
+
<h3 id="threadLoader"><a href="#threadLoader" class="headerlink" title="threadLoader"></a>threadLoader</h3><p>测试效果变差了 😅,线程数越小编译速度越快</p>
|
|
1095
|
+
<p>config/threadLoader.js</p>
|
|
1096
|
+
<pre><code>module.exports = (config, resolve) => {
|
|
1097
|
+
const baseRule = config.module.rule('js').test(/.js|.tsx?$/);
|
|
1098
|
+
return() => {
|
|
1099
|
+
const useThreads = true;
|
|
1100
|
+
if (useThreads) {
|
|
1101
|
+
const threadLoaderConfig = baseRule
|
|
1102
|
+
.use('thread-loader')
|
|
1103
|
+
.loader('thread-loader');
|
|
1104
|
+
threadLoaderConfig.options({ workers: 3 })
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
</code></pre>
|
|
1109
|
+
<hr>
|
|
1110
|
+
<h2 id="课时-7:多页面配置"><a href="#课时-7:多页面配置" class="headerlink" title="课时 7:多页面配置"></a>课时 7:多页面配置</h2><p><strong>注意</strong></p>
|
|
1111
|
+
<ul>
|
|
1112
|
+
<li><code>弃用</code> npm run build & npm run dev & npm run dll</li>
|
|
1113
|
+
<li><code>改成</code> box build & box dev & box dll</li>
|
|
1114
|
+
<li><code>link</code> npm link 将 box 命令链接到全局</li>
|
|
1115
|
+
</ul>
|
|
1116
|
+
<p>本章内容</p>
|
|
1117
|
+
<ol>
|
|
1118
|
+
<li>使用</li>
|
|
1119
|
+
<li>改造为脚手架</li>
|
|
1120
|
+
<li>多页面配置</li>
|
|
1121
|
+
</ol>
|
|
1122
|
+
<h3 id="使用-1"><a href="#使用-1" class="headerlink" title="使用"></a>使用</h3><pre><code>box build # 不加参数则会编译所有页面,并清空 dist
|
|
1123
|
+
box dev # 默认编译 index 页面
|
|
1124
|
+
</code></pre>
|
|
1125
|
+
<p><code>参数</code></p>
|
|
1126
|
+
<pre><code># index2 是指定编译的页面。不会清空 dist
|
|
1127
|
+
# report 开启打包分析
|
|
1128
|
+
box build index2 --report
|
|
1129
|
+
box dev index2 --report
|
|
1130
|
+
</code></pre>
|
|
1131
|
+
<h3 id="改造为脚手架"><a href="#改造为脚手架" class="headerlink" title="改造为脚手架"></a>改造为脚手架</h3><p>分成三个命令,进行不同操作</p>
|
|
1132
|
+
<ul>
|
|
1133
|
+
<li>build</li>
|
|
1134
|
+
<li>dev</li>
|
|
1135
|
+
<li>dll</li>
|
|
1136
|
+
</ul>
|
|
1137
|
+
<p>bin/box.js</p>
|
|
1138
|
+
<pre><code>#!/usr/bin/env node
|
|
1139
|
+
|
|
1140
|
+
const chalk = require('chalk')
|
|
1141
|
+
const program = require('commander')
|
|
1142
|
+
const packageConfig = require('../package.json');
|
|
1143
|
+
const { cleanArgs } = require('../lib')
|
|
1144
|
+
const path = require('path')
|
|
1145
|
+
const __name__ = `build,dev,dll`
|
|
1146
|
+
|
|
1147
|
+
let boxConf = {}
|
|
1148
|
+
let lock = false
|
|
1149
|
+
|
|
1150
|
+
try {
|
|
1151
|
+
boxConf = require(path.join(process.cwd(), 'box.config.js'))()
|
|
1152
|
+
} catch (error) { }
|
|
1153
|
+
|
|
1154
|
+
program
|
|
1155
|
+
.usage('<command> [options]')
|
|
1156
|
+
.version(packageConfig.version)
|
|
1157
|
+
.command('build [app-page]')
|
|
1158
|
+
.description(`构建开发环境`)
|
|
1159
|
+
.option('-r, --report', '打包分析报告')
|
|
1160
|
+
.option('-d, --dll', '合并差分包')
|
|
1161
|
+
.action(async (name, cmd) => {
|
|
1162
|
+
const options = cleanArgs(cmd)
|
|
1163
|
+
const args = Object.assign(options, { name }, boxConf)
|
|
1164
|
+
if (lock) return
|
|
1165
|
+
lock = true;
|
|
1166
|
+
if (boxConf.pages) {
|
|
1167
|
+
Object.keys(boxConf.pages).forEach(page => {
|
|
1168
|
+
args.name = page;
|
|
1169
|
+
require('../build/build')(args)
|
|
1170
|
+
})
|
|
1171
|
+
} else {
|
|
1172
|
+
require('../build/build')(args)
|
|
1173
|
+
}
|
|
1174
|
+
})
|
|
1175
|
+
|
|
1176
|
+
program
|
|
1177
|
+
.usage('<command> [options]')
|
|
1178
|
+
.version(packageConfig.version)
|
|
1179
|
+
.command('dev [app-page]')
|
|
1180
|
+
.description(`构建生产环境`)
|
|
1181
|
+
.option('-d, --dll', '合并差分包')
|
|
1182
|
+
.action(async (name, cmd) => {
|
|
1183
|
+
const options = cleanArgs(cmd)
|
|
1184
|
+
const args = Object.assign(options, { name }, boxConf)
|
|
1185
|
+
if (lock) return
|
|
1186
|
+
lock = true;
|
|
1187
|
+
require('../build/dev')(args)
|
|
1188
|
+
})
|
|
1189
|
+
|
|
1190
|
+
program
|
|
1191
|
+
.usage('<command> [options]')
|
|
1192
|
+
.version(packageConfig.version)
|
|
1193
|
+
.command('dll [app-page]')
|
|
1194
|
+
.description(`编译差分包`)
|
|
1195
|
+
.action(async (name, cmd) => {
|
|
1196
|
+
const options = cleanArgs(cmd)
|
|
1197
|
+
const args = Object.assign(options, { name }, boxConf)
|
|
1198
|
+
if (lock) return
|
|
1199
|
+
lock = true;
|
|
1200
|
+
require('../build/dll')(args)
|
|
1201
|
+
})
|
|
1202
|
+
|
|
1203
|
+
program.parse(process.argv).args && program.parse(process.argv).args[0];
|
|
1204
|
+
program.commands.forEach(c => c.on('--help', () => console.log()))
|
|
1205
|
+
|
|
1206
|
+
if (process.argv[2] && !__name__.includes(process.argv[2])) {
|
|
1207
|
+
console.log()
|
|
1208
|
+
console.log(chalk.red(` 没有找到 ${process.argv[2]} 命令`))
|
|
1209
|
+
console.log()
|
|
1210
|
+
program.help()
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
if (!process.argv[2]) {
|
|
1214
|
+
program.help()
|
|
1215
|
+
}
|
|
1216
|
+
</code></pre>
|
|
1217
|
+
<h3 id="多页面配置"><a href="#多页面配置" class="headerlink" title="多页面配置"></a>多页面配置</h3><p>box.config.js</p>
|
|
1218
|
+
<pre><code>module.exports = function (config) {
|
|
1219
|
+
return {
|
|
1220
|
+
entry: 'src/main.js', // 默认入口
|
|
1221
|
+
dist: 'dist', // 默认打包目录
|
|
1222
|
+
publicPath: '/',
|
|
1223
|
+
port: 8888,
|
|
1224
|
+
pages: {
|
|
1225
|
+
index: {
|
|
1226
|
+
entry: 'src/main.js',
|
|
1227
|
+
template: 'public/index.html',
|
|
1228
|
+
filename: 'index.html',
|
|
1229
|
+
},
|
|
1230
|
+
index2: {
|
|
1231
|
+
entry: 'src/main.js',
|
|
1232
|
+
template: 'public/index2.html',
|
|
1233
|
+
filename: 'index2.html',
|
|
1234
|
+
}
|
|
1235
|
+
},
|
|
1236
|
+
chainWebpack(config) {
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
</code></pre>
|
|
1241
|
+
<hr>
|
|
1242
|
+
<h2 id="课时-8:手写一个webpack插件"><a href="#课时-8:手写一个webpack插件" class="headerlink" title="课时 8:手写一个webpack插件"></a>课时 8:手写一个webpack插件</h2><p>如果把 webpack 当成一个垃圾工厂,loader 就是垃圾分类,将所有垃圾整理好交给 webpack。plugin 就是如何去处理这些垃圾。</p>
|
|
1243
|
+
<p>webpack 插件写起来很简单,就是你要知道各种各样的钩子在什么时候触发,然后你的逻辑写在钩子里面就ok了</p>
|
|
1244
|
+
<ul>
|
|
1245
|
+
<li><code>apply</code> 函数是 webpack 在调用 plugin 的时候执行的,你可以认为它是入口</li>
|
|
1246
|
+
<li><code>compiler</code> 暴露了和 webpack 整个生命周期相关的钩子</li>
|
|
1247
|
+
<li><code>Compilation</code> 暴露了与模块和依赖有关的粒度更小的事件钩子</li>
|
|
1248
|
+
</ul>
|
|
1249
|
+
<p><strong>本节概要</strong></p>
|
|
1250
|
+
<ul>
|
|
1251
|
+
<li>实现一个 CopyPlugin</li>
|
|
1252
|
+
<li>使用</li>
|
|
1253
|
+
</ul>
|
|
1254
|
+
<h3 id="实现一个-CopyPlugin"><a href="#实现一个-CopyPlugin" class="headerlink" title="实现一个 CopyPlugin"></a>实现一个 CopyPlugin</h3><p>我们今天写一个 copy 的插件,在webpack构建完成之后,将目标目录下的文件 copy 到另一个目录下</p>
|
|
1255
|
+
<pre><code>const fs = require('fs-extra')
|
|
1256
|
+
const globby = require('globby')
|
|
1257
|
+
|
|
1258
|
+
classCopyDirWebpackPlugin{
|
|
1259
|
+
constructor(options) {
|
|
1260
|
+
this.options = options;
|
|
1261
|
+
}
|
|
1262
|
+
apply(compiler) {
|
|
1263
|
+
const opt = this.options
|
|
1264
|
+
compiler.plugin('done', (stats) => {
|
|
1265
|
+
if (process.env.NODE_ENV === 'production') {
|
|
1266
|
+
(async ()=>{
|
|
1267
|
+
const toFilesPath = await globby([`${opt.to}/**`, '!.git/**'])
|
|
1268
|
+
toFilesPath.forEach(filePath => fs.removeSync(filePath))
|
|
1269
|
+
const fromFilesPath = await globby([`${opt.from}/**`])
|
|
1270
|
+
fromFilesPath.forEach(fromPath => {
|
|
1271
|
+
const cachePath = fromPath
|
|
1272
|
+
fromPath = fromPath.replace('dist', opt.to)
|
|
1273
|
+
const dirpaths = fromPath.substring(0, fromPath.lastIndexOf('/'))
|
|
1274
|
+
fs.mkdirpSync(dirpaths)
|
|
1275
|
+
fs.copySync(cachePath, fromPath)
|
|
1276
|
+
})
|
|
1277
|
+
console.log(` 完成copy ${opt.from} to ${opt.to}`)
|
|
1278
|
+
})()
|
|
1279
|
+
}
|
|
1280
|
+
});
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
module.exports = CopyDirWebpackPlugin
|
|
1285
|
+
</code></pre>
|
|
1286
|
+
<h3 id="使用-2"><a href="#使用-2" class="headerlink" title="使用"></a>使用</h3><p>将打包出来的 dist 目录下的内容 copy 到 dist2 目录下</p>
|
|
1287
|
+
<pre><code>const CopyPlugin = require('../webapck-plugin-copy');
|
|
1288
|
+
|
|
1289
|
+
module.exports = ({ config }) => {
|
|
1290
|
+
return() => {
|
|
1291
|
+
config.plugin('copy-dist')
|
|
1292
|
+
.use(CopyPlugin, [{
|
|
1293
|
+
from: 'dist',
|
|
1294
|
+
to: 'dist2'
|
|
1295
|
+
}])
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
</code></pre>
|
|
1299
|
+
<hr>
|
|
1300
|
+
<h2 id="课时-9:构建-ssr"><a href="#课时-9:构建-ssr" class="headerlink" title="课时 9:构建 ssr"></a>课时 9:构建 ssr</h2><p>ssr 就是服务端渲染,做 ssr 的好处就是为了处理 spa 的不足,比如 seo 优化,服务端缓存等问题。</p>
|
|
1301
|
+
<p>今天主要用 react 的 ssr 来做一个简单的实例,让大家更清晰的入门</p>
|
|
1302
|
+
<p>本章概要</p>
|
|
1303
|
+
<ul>
|
|
1304
|
+
<li>创建 box build:ssr</li>
|
|
1305
|
+
<li>编译 ssr</li>
|
|
1306
|
+
<li>编译 jsx 语法</li>
|
|
1307
|
+
<li>入口区分服务端/客户端</li>
|
|
1308
|
+
<li>服务端渲染</li>
|
|
1309
|
+
<li>小结</li>
|
|
1310
|
+
</ul>
|
|
1311
|
+
<h3 id="创建-box-build-ssr"><a href="#创建-box-build-ssr" class="headerlink" title="创建 box build:ssr"></a>创建 box build:ssr</h3><p>老规矩,先来一个 <code>box build:ssr</code> 命令让程序可以执行</p>
|
|
1312
|
+
<p>执行 <code>box build:ssr</code> 会调用 <code>build/ssr</code> 执行编译</p>
|
|
1313
|
+
<pre><code>program
|
|
1314
|
+
.usage('<command> [options]')
|
|
1315
|
+
.version(packageConfig.version)
|
|
1316
|
+
.command('build:ssr [app-page]')
|
|
1317
|
+
.description(`服务端渲染`)
|
|
1318
|
+
.action(async (name, cmd) => {
|
|
1319
|
+
const options = cleanArgs(cmd);
|
|
1320
|
+
const args = Object.assign(options, { name }, boxConf);
|
|
1321
|
+
if (lock) return;
|
|
1322
|
+
lock = true;
|
|
1323
|
+
require('../build/ssr')(args);
|
|
1324
|
+
});
|
|
1325
|
+
</code></pre>
|
|
1326
|
+
<h3 id="编译-ssr"><a href="#编译-ssr" class="headerlink" title="编译 ssr"></a>编译 ssr</h3><p>与其他的编译没有什么区别,值得住的是</p>
|
|
1327
|
+
<ul>
|
|
1328
|
+
<li><p>target 指定为 umd 模式</p>
|
|
1329
|
+
</li>
|
|
1330
|
+
<li><p>globalObject 为 this</p>
|
|
1331
|
+
</li>
|
|
1332
|
+
<li><p>入口改为 ssr.jsx</p>
|
|
1333
|
+
<p> .libraryTarget(‘umd’)<br> .globalObject(‘this’)</p>
|
|
1334
|
+
</li>
|
|
1335
|
+
</ul>
|
|
1336
|
+
<p>build/ssr.js</p>
|
|
1337
|
+
<pre><code>module.exports = function(options) {
|
|
1338
|
+
const path = require('path');
|
|
1339
|
+
const Config = require('webpack-chain');
|
|
1340
|
+
const config = new Config();
|
|
1341
|
+
const webpack = require('webpack');
|
|
1342
|
+
const rimraf = require('rimraf');
|
|
1343
|
+
const ora = require('ora');
|
|
1344
|
+
const chalk = require('chalk');
|
|
1345
|
+
const PATHS = {
|
|
1346
|
+
build: path.join(process.cwd(), 'static'),
|
|
1347
|
+
ssrDemo: path.join(process.cwd(), 'src', 'ssr.jsx')
|
|
1348
|
+
};
|
|
1349
|
+
|
|
1350
|
+
require('../config/babelLoader')({ config, tsx: true })();
|
|
1351
|
+
require('../config/HtmlWebpackPlugin')({
|
|
1352
|
+
config,
|
|
1353
|
+
options: {
|
|
1354
|
+
publicPath: '/',
|
|
1355
|
+
filename: 'client.ssr.html'
|
|
1356
|
+
}
|
|
1357
|
+
})();
|
|
1358
|
+
|
|
1359
|
+
config
|
|
1360
|
+
.entry('ssr')
|
|
1361
|
+
.add(PATHS.ssrDemo)
|
|
1362
|
+
.end()
|
|
1363
|
+
.set('mode', 'development') // production
|
|
1364
|
+
.output.path(PATHS.build)
|
|
1365
|
+
.filename('[name].js')
|
|
1366
|
+
.libraryTarget('umd')
|
|
1367
|
+
.globalObject('this')
|
|
1368
|
+
.library('[name]')
|
|
1369
|
+
.end();
|
|
1370
|
+
|
|
1371
|
+
rimraf.sync(path.join(process.cwd(), PATHS.build));
|
|
1372
|
+
const spinner = ora('开始构建项目...');
|
|
1373
|
+
spinner.start();
|
|
1374
|
+
|
|
1375
|
+
webpack(config.toConfig(), function(err, stats) {
|
|
1376
|
+
spinner.stop();
|
|
1377
|
+
if (err) throw err;
|
|
1378
|
+
process.stdout.write(
|
|
1379
|
+
stats.toString({
|
|
1380
|
+
colors: true,
|
|
1381
|
+
modules: false,
|
|
1382
|
+
children: false,
|
|
1383
|
+
chunks: false,
|
|
1384
|
+
chunkModules: false
|
|
1385
|
+
}) + '\n\n'
|
|
1386
|
+
);
|
|
1387
|
+
|
|
1388
|
+
if (stats.hasErrors()) {
|
|
1389
|
+
console.log(chalk.red('构建失败\n'));
|
|
1390
|
+
process.exit(1);
|
|
1391
|
+
}
|
|
1392
|
+
console.log(chalk.cyan('build完成\n'));
|
|
1393
|
+
});
|
|
1394
|
+
};
|
|
1395
|
+
</code></pre>
|
|
1396
|
+
<h3 id="编译-jsx-语法"><a href="#编译-jsx-语法" class="headerlink" title="编译 jsx 语法"></a>编译 jsx 语法</h3><p>因为我们是用 react 写的,避免不了会用到 jsx 语法,所以我们需要在 <code>babel-loader</code> 中使用 <code>@babel/preset-react</code></p>
|
|
1397
|
+
<pre><code>npm i @babel/preset-react -D
|
|
1398
|
+
</code></pre>
|
|
1399
|
+
<p>config/babelLoader.js</p>
|
|
1400
|
+
<pre><code>if (tsx) {
|
|
1401
|
+
babelConf.presets.push('@babel/preset-react');
|
|
1402
|
+
}
|
|
1403
|
+
</code></pre>
|
|
1404
|
+
<h3 id="入口区分服务端-x2F-客户端"><a href="#入口区分服务端-x2F-客户端" class="headerlink" title="入口区分服务端/客户端"></a>入口区分服务端/客户端</h3><p>区分服务端跟客户端分别渲染</p>
|
|
1405
|
+
<pre><code>const React = require("react");
|
|
1406
|
+
const ReactDOM = require("react-dom");
|
|
1407
|
+
|
|
1408
|
+
const SSR = <divonClick={() => alert("hello")}>Hello world</div>;
|
|
1409
|
+
|
|
1410
|
+
if (typeofdocument === "undefined") {
|
|
1411
|
+
console.log('在服务端渲染')
|
|
1412
|
+
module.exports = SSR;
|
|
1413
|
+
} else {
|
|
1414
|
+
console.log('在客户端渲染')
|
|
1415
|
+
const renderMethod = !module.hot ? ReactDOM.render : ReactDOM.hydrate;
|
|
1416
|
+
renderMethod(SSR, document.getElementById("app"));
|
|
1417
|
+
}
|
|
1418
|
+
</code></pre>
|
|
1419
|
+
<h3 id="服务端渲染"><a href="#服务端渲染" class="headerlink" title="服务端渲染"></a>服务端渲染</h3><ul>
|
|
1420
|
+
<li><p>将打包出来的 static 文件夹作为一个服务</p>
|
|
1421
|
+
</li>
|
|
1422
|
+
<li><p>访问 <code>http://127.0.0.1:8080</code>,进入服务端渲染的页面</p>
|
|
1423
|
+
</li>
|
|
1424
|
+
<li><p>再执行一遍 ssr.js 进行事件绑定</p>
|
|
1425
|
+
<p> module.exports = function (options) {<br> const express = require(“express”);<br> const { renderToString } = require(“react-dom/server”);<br> const chalk = require(‘chalk’)</p>
|
|
1426
|
+
<p> const SSR = require(“../static/ssr”);<br> const port = process.env.PORT || 8080;</p>
|
|
1427
|
+
<pre><code>server(port);
|
|
1428
|
+
</code></pre>
|
|
1429
|
+
<p> functionserver(port) {<br> const app = express();<br> app.use(express.static(“static”));<br> app.get(“/“, (req, res) =><br> res.status(200).send(renderMarkup(renderToString(SSR)))<br> );<br> const empty = ‘ ‘<br> const common = <code>App running at: - Local: http://127.0.0.1:${port}\n</code><br> console.log(chalk.cyan(‘\n’ + empty + common))</p>
|
|
1430
|
+
<pre><code> app.listen(port, () => process.send && process.send("online"));
|
|
1431
|
+
}
|
|
1432
|
+
</code></pre>
|
|
1433
|
+
<p> functionrenderMarkup(html) {<br> return<code><!DOCTYPE html> <html> <head> <title>Webpack SSR Demo</title> <meta charset="utf-8" /> </head> <body> <div id="app">${html}</div> <script src="./ssr.js"></script> </body> </html></code>;<br>}<br> }</p>
|
|
1434
|
+
</li>
|
|
1435
|
+
</ul>
|
|
1436
|
+
<h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><p>至此 ssr 已经结束了,其实所有看起来很高大上的技术都是从一点一滴积累起来的,只要我们明白原理,你也能做出更优秀的框架</p>
|
|
1437
|
+
<p><strong>推荐阅读</strong></p>
|
|
1438
|
+
<p>(点击标题可跳转阅读)</p>
|
|
1439
|
+
<p><a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzAxODE2MjM1MA==&mid=2651557158&idx=1&sn=37b5709e442840d03391049fccebedbb&chksm=80255ae7b752d3f1a37ca753aeeacdde8502d525e86f5ae1b0fbb2a780c3c4e627a16fbc05b0&scene=21#wechat_redirect">一看就懂之 webpack 高级配置与优化</a></p>
|
|
1440
|
+
<p><a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzAxODE2MjM1MA==&mid=2651557068&idx=1&sn=570f0ab7f1cf096f22d4446058f86279&chksm=80255b0db752d21bfbf5930e997341da18c3c36c6387711806b4d0404b54ecfcfccade114bc3&scene=21#wechat_redirect">一文搞懂 Webpack 多入口配置</a></p>
|
|
1441
|
+
<p><a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzAxODE2MjM1MA==&mid=2651557043&idx=2&sn=531feefa5133f279ccae6242ea16e0c0&chksm=80255b72b752d264692bfee6d71ed582cef914c526fc18b5ddcc841cc4f64c07e447dff06a42&scene=21#wechat_redirect">愿未来没有 Webpack</a></p>
|
|
1442
|
+
<p>觉得本文对你有帮助?请分享给更多人</p>
|
|
1443
|
+
<p>关注「前端大全」加星标,提升前端技能</p>
|
|
1444
|
+
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/zPh0erYjkib3jzxuxibgz8GJ7k0UVp4kHMMDsL32ialKjpkgA9q7BFkbJ8VRYh0Liad1x6rD7w6oU7icR9DVPgsIxzQ/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1" alt="xuehuayu.cn"></p>
|
|
1445
|
+
<p>好文章,我在看❤️</p>
|
|
1446
|
+
</div><iframe src="/donate/?AliPayQR=https://store.xuehuayu.cn/store/public/alipay_qr.jpg&WeChatQR=https://store.xuehuayu.cn/store/public/wechat_qr.png&UnionPayQR=null&GitHub=null&BTCQR=null&BTCKEY=null&PayPal=https://www.paypal.com/paypalme/OrdinaryPerson" style="overflow-x:hidden; overflow-y:hidden; border:0xp none #fff; min-height:240px; width:100%;" frameborder="0" scrolling="no"></iframe><div><ul class="post-copyright"><li class="post-copyright-author"><strong>本文作者:</strong>雪花雨</li><li class="post-copyright-link"><strong>本文链接:</strong><a href="/article/25164.html">https://xuehuayu.cn/article/25164.html</a></li><li class="post-copyright-license"><strong>版权声明:</strong>① 标为原创的文章为博主原创,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接。② 转载文章来自网络,已标明出处,<a href="mailto:cainiaoblog@126.com">侵删</a>。</li></ul></div><br><div class="tags"><a href="/tags/%E8%BD%AC%E8%BD%BD/">转载</a></div><div class="post-nav"><a class="pre" href="/article/54912.html">浏览器与前端性能灵魂之问,请问你能接得住几个?</a><a class="next" href="/article/26083.html">OSCHINA 公布 2019 年度最受欢迎中国开源软件</a></div><div id="vcomment"></div><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@waline/client@v2/dist/waline.min.css"><script src="//cdn.jsdelivr.net/npm/@waline/client@v2/dist/waline.js"></script><script>const origin = window.location.origin
|
|
1447
|
+
const serverURL = origin.includes('cainiaoblog') ? 'https://cmts.cainiaoblog.cn' : 'https://cmts.xuehuayu.cn'
|
|
1448
|
+
const locale = {
|
|
1449
|
+
placeholder: '请正确填写昵称和邮箱,方便接收回复通知~',
|
|
1450
|
+
sofa: '沙发空缺中,还不快抢~',
|
|
1451
|
+
};
|
|
1452
|
+
Waline.init({
|
|
1453
|
+
el: '#vcomment',
|
|
1454
|
+
serverURL: serverURL,
|
|
1455
|
+
pageSize: '20',
|
|
1456
|
+
visitor: true, // 阅读量统计
|
|
1457
|
+
requiredMeta: ['nick', 'mail'],
|
|
1458
|
+
emoji: [
|
|
1459
|
+
'https://cdn.jsdelivr.net/gh/walinejs/emojis@1.0.0/weibo',
|
|
1460
|
+
'https://cdn.jsdelivr.net/gh/walinejs/emojis@1.0.0/qq',
|
|
1461
|
+
'https://cdn.jsdelivr.net/gh/walinejs/emojis@1.0.0/tw-emoji',
|
|
1462
|
+
],
|
|
1463
|
+
locale,
|
|
1464
|
+
pageview: true
|
|
1465
|
+
})
|
|
1466
|
+
</script></div></div></div><div class="pure-u-1 pure-u-md-1-4 fixed-search hidden_mid_and_down"><div id="sidebar"><script type="text/javascript" src="//cdn.staticaly.com/gh/npljy/npljy.github.io/main/js/search.min.js"></script><div class="widget"><div class="widget-search"><input class="search" type="radio" name="search" value="baidu" id="baidu" checked="checked"/><label class="label" for="baidu" title="百度全站搜索">百度</label><input class="search" type="radio" name="search" value="google" id="google"/><label class="label" for="google" title="谷歌全站搜索">谷歌</label><input class="search" type="radio" name="search" value="self" id="self"/><label class="label" for="self" title="使用站内搜索">站内</label></div><div class="widget" id="search"><form class="search-form" action="//www.baidu.com/baidu" method="get" accept-charset="utf-8" target="_blank"><input type="search" name="word" maxlength="30" placeholder="百度全站搜索"/><input type="hidden" name="si" value="xuehuayu.cn"/><input type="hidden" name="cl" value="3"/><input type="hidden" name="ct" value="2097152"/><input type="hidden" name="s" value="on"/><input class="search-submit" type="submit" value=""/></form></div></div><script>$('input[type=radio][name=search]').change(function() {
|
|
1467
|
+
var val = $(this).val()
|
|
1468
|
+
var self = '<div class="search-form"><input id="local-search-input" placeholder="站内搜索,首次慢" type="search" name="q" results="0"><input class="search-submit" type="submit" value=""/><div id="local-search-result"></div></div>'
|
|
1469
|
+
var google = '<form class="search-form" action="//www.google.com/search" method="get" accept-charset="utf-8" target="_blank"><input type="search" name="q" maxlength="30" placeholder="谷歌全站搜索"><input type="hidden" name="sitesearch" value="xuehuayu.cn"><input class="search-submit" type="submit" value=""/></form>'
|
|
1470
|
+
var baidu = '<form class="search-form" action="//www.baidu.com/baidu" method="get" accept-charset="utf-8" target="_blank"><input type="search" name="word" maxlength="30" placeholder="百度全站搜索"><input type="hidden" name="si" value="xuehuayu.cn"><input type="hidden" name="cl" value="3"><input type="hidden" name="ct" value="2097152"><input type="hidden" name="s" value="on"><input class="search-submit" type="submit" value=""/></form>'
|
|
1471
|
+
|
|
1472
|
+
if (val === 'self') {
|
|
1473
|
+
$('#search').html(self)
|
|
1474
|
+
var search_path = 'search.xml';
|
|
1475
|
+
if (search_path.length == 0) {
|
|
1476
|
+
search_path = 'search.xml';
|
|
1477
|
+
}
|
|
1478
|
+
var path = 'https://cdn.staticaly.com/gh/npljy/npljy.github.io/main/' + search_path;
|
|
1479
|
+
searchFunc(path, 'local-search-input', 'local-search-result');
|
|
1480
|
+
} else if (val === 'baidu') {
|
|
1481
|
+
$('#search').html(baidu)
|
|
1482
|
+
} else if (val === 'google') {
|
|
1483
|
+
$('#search').html(google)
|
|
1484
|
+
}
|
|
1485
|
+
})</script><div class="widget widget-wxmp"><img alt="微信公众号" width="100%" src="//cdn.staticaly.com/gh/npljy/npljy.github.io/main/img/mp-mini.jpg"/></div><div class="widget widget-categories"><div class="widget-title"><i class="fa fa-folder-o"> 分类</i></div><ul class="category-list"><li class="category-list-item"><a class="category-list-link" href="/categories/CDN/">CDN</a><span class="category-list-count">2</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/IDE/">IDE</a><span class="category-list-count">1</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/git/">git</a><span class="category-list-count">2</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/hexo/">hexo</a><span class="category-list-count">1</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/%E5%85%AC%E5%85%B1%E8%8A%82%E6%97%A5/">公共节日</a><span class="category-list-count">1</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/%E5%88%86%E4%BA%AB/">分享</a><span class="category-list-count">29</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/%E5%89%8D%E7%AB%AF/">前端</a><span class="category-list-count">191</span><ul class="category-list-child"><li class="category-list-item"><a class="category-list-link" href="/categories/%E5%89%8D%E7%AB%AF/%E6%B5%8F%E8%A7%88%E5%99%A8/">浏览器</a><span class="category-list-count">1</span><ul class="category-list-child"><li class="category-list-item"><a class="category-list-link" href="/categories/%E5%89%8D%E7%AB%AF/%E6%B5%8F%E8%A7%88%E5%99%A8/%E7%BD%91%E7%BB%9C%E5%8D%8F%E8%AE%AE/">网络协议</a><span class="category-list-count">1</span></li></ul></li></ul></li><li class="category-list-item"><a class="category-list-link" href="/categories/%E5%8D%9A%E5%AE%A2/">博客</a><span class="category-list-count">3</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/%E5%B7%A5%E5%85%B7/">工具</a><span class="category-list-count">2</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/%E5%B9%BF%E5%91%8A%E8%BF%87%E6%BB%A4/">广告过滤</a><span class="category-list-count">8</span><ul class="category-list-child"><li class="category-list-item"><a class="category-list-link" href="/categories/%E5%B9%BF%E5%91%8A%E8%BF%87%E6%BB%A4/%E8%A7%86%E9%A2%91%E7%94%B5%E8%A7%86%E5%B0%8F%E8%AF%B4%E6%BC%AB%E7%94%BB%E8%B5%84%E8%AE%AF%E9%9F%B3%E4%B9%90/">视频电视小说漫画资讯音乐</a><span class="category-list-count">2</span></li></ul></li><li class="category-list-item"><a class="category-list-link" href="/categories/%E6%AD%A3%E5%88%99/">正则</a><span class="category-list-count">5</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/%E6%B5%8F%E8%A7%88%E5%99%A8/">浏览器</a><span class="category-list-count">5</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/%E7%AE%97%E6%B3%95/">算法</a><span class="category-list-count">2</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/%E8%B5%84%E8%AE%AF/">资讯</a><span class="category-list-count">16</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/%E8%BD%BB%E6%9D%BE%E4%B8%80%E5%88%BB/">轻松一刻</a><span class="category-list-count">1</span></li></ul></div><div class="widget widget-tags"><div class="widget-title"><i class="fa fa-star-o"> 标签</i></div><div class="tagcloud"><a href="/tags/%E5%8E%9F%E5%88%9B/" style="font-size: 15px;">原创</a> <a href="/tags/%E8%BD%AC%E8%BD%BD/" style="font-size: 15px;">转载</a> <a href="/tags/%E6%89%8B%E5%86%99/" style="font-size: 15px;">手写</a> <a href="/tags/%E7%96%AB%E6%83%85/" style="font-size: 15px;">疫情</a> <a href="/tags/%E5%A8%B1%E4%B9%90/" style="font-size: 15px;">娱乐</a></div></div><div class="widget widget-recent-posts"><div class="widget-title"><i class="fa fa-file-o"> 最近文章</i></div><ul class="post-list"><li class="post-list-item"><a class="post-list-link" href="/article/c5576490.html">面试准备</a></li><li class="post-list-item"><a class="post-list-link" href="/article/84c9ccd6.html">精读《SolidJS》</a></li><li class="post-list-item"><a class="post-list-link" href="/article/53803e6.html">git提交注释规范</a></li><li class="post-list-item"><a class="post-list-link" href="/article/a4145266.html">前端数组拍平flat一行代码</a></li><li class="post-list-item"><a class="post-list-link" href="/article/bedea419.html">一道前端this面试题</a></li><li class="post-list-item"><a class="post-list-link" href="/article/bf7e7421.html">git多账号配置</a></li></ul></div><div class="widget widget-links"><div class="widget-title"><i class="fa fa-external-link"> 友情链接</i></div><ul></ul><a href="https://github.com/npljy?utm_source=xuehuayu.cn" title="GitHub" target="_blank">GitHub</a><ul></ul><a href="https://laonongmin.online?utm_source=xuehuayu.cn" title="看看" target="_blank">看看</a><ul></ul><a href="https://cainiaoblog.cn?utm_source=xuehuayu.cn" title="菜鸟博客" target="_blank">菜鸟博客</a><ul></ul><a href="mailto:cainiaoblog@126.com" title="友链联系" target="_blank">友链联系</a></div></div></div><div class="pure-u-1 pure-u-md-3-4"><div id="footer"><div class="flex-block justify-center align-center flex-wrap"><a class="gxba-link" id="gxba" rel="nofollow" target="_blank" href="http://beian.miit.gov.cn/">京ICP备20007647号-2</a><a class="gaba-link" id="gaba-link" rel="nofollow" target="_blank" href="http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=11010802031264"><img class="nofancybox" src="//cdn.staticaly.com/gh/npljy/npljy.github.io/main/img/gaba.png" alt=""/><span id="gaba">京公网安备 11010802031264号</span><span style="padding-right: 10px;"></span></a><span>Copyright © 2023 </span><a href="/." rel="nofollow">前端壹菜鸟. </a><script>(function(){
|
|
1486
|
+
var cnb =window.location.origin.indexOf('cainiaoblog') !==-1
|
|
1487
|
+
if (cnb) {
|
|
1488
|
+
var gxba =document.getElementById('gxba')
|
|
1489
|
+
var gaba =document.getElementById('gaba')
|
|
1490
|
+
var gabaLink =document.getElementById('gaba-link')
|
|
1491
|
+
gxba.innerText ='京ICP备20007647号-1'
|
|
1492
|
+
gaba.innerText ='京公网安备 11010802031254号'
|
|
1493
|
+
gabaLink.setAttribute('href','http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=11010802031254')
|
|
1494
|
+
}
|
|
1495
|
+
})()</script></div><div class="flex-block justify-center align-center flex-wrap"><a rel="nofollow" target="_blank" href="https://v6.51.la/s/Guz3lwHCsVme9Cu"><div class="busuanzi_container" id="busuanzi_container"><span class="busuanzi_container_site_pv" id="busuanzi_container_site_pv">访问量:<span id="busuanzi_value_site_pv"><i class="fa fa-spinner"></i></span></span><span class="busuanzi_container_site_uv" id="busuanzi_container_site_uv">访客数:<span id="busuanzi_value_site_uv"><i class="fa fa-spinner"></i></span></span><span style="padding-right: 10px;"></span></div></a><a rel="nofollow" target="_blank" href="https://www.upyun.com/?utm_source=lianmeng&utm_medium=referral"><span style="font-weight:bold;letter-spacing: 1px;">本网站由 </span><img class="nofancybox" height="32" style="vertical-align: middle" src="//cdn.staticaly.com/gh/npljy/npljy.github.io/main/img/upy_logo.min.svg" alt=""/><span style="font-weight:bold;letter-spacing: 1px;">提供CDN加速/云存储服务</span></a><span style="padding-right: 10px;"></span><a rel="nofollow" style="font-size:18px;font-weight:bold;vertical-align: middle" target="_blank" title="注册就送代金券可直接使用" href="https://console.upyun.com/register/?invite=HyDsjZHIL">注册</a></div></div></div></div><a class="show" id="rocket" href="#top"></a><div class="darkmode-toggle">🌓</div><script type="text/javascript" src="//cdn.staticaly.com/gh/npljy/npljy.github.io/main/js/totop.min.js" async></script><script type="text/javascript" src="//cdn.staticaly.com/gh/npljy/npljy.github.io/main/js/dark.min.js" async></script><script type="text/javascript" src="//cdn.staticaly.com/gh/npljy/npljy.github.io/main/js/codeblock-resizer.min.js"></script><script type="text/javascript" src="//cdn.staticaly.com/gh/npljy/npljy.github.io/main/js/smartresize.min.js"></script></div></body></html>
|