摘要:后來換了一家公司,沒有前端開發(fā)這個(gè)職位,是從轉(zhuǎn)過去的,因?yàn)轫?xiàng)目需要,漸漸的也就坐實(shí)了這個(gè)崗位。假如我們以前的代碼是這樣的放到全局作用域。此時(shí)的代碼,其實(shí)已經(jīng)具備了進(jìn)入現(xiàn)代期的要求,那就是規(guī)范模塊化。
我是14年入的程序員大軍,當(dāng)時(shí)主java兼具前端開發(fā)的活兒,在現(xiàn)在看來的一些流開發(fā)框架和新興思想,早在node.js開始進(jìn)入大家視野的時(shí)候就流行起來了,只是在那時(shí)博主并沒有關(guān)注前端的生態(tài)圈(然而java好像也并沒有關(guān)注,逃),所以還是處在很多人所描述的刀耕火種的階段,前端代碼全部掛載到全局作用域,包括插件導(dǎo)出的變量。那更別提組件化和模塊化的編程思想了,甚至代碼都不用壓縮優(yōu)化就直接上傳到服務(wù)器發(fā)布了。
后來換了一家公司,沒有前端開發(fā)這個(gè)職位,是從javaer轉(zhuǎn)過去的,因?yàn)轫?xiàng)目需要,漸漸的也就坐實(shí)了這個(gè)崗位。項(xiàng)目到現(xiàn)在(2014年8月-2017年7月22日)一共出現(xiàn)了三個(gè)階段
用著十年前的開發(fā)(或者叫整合)技術(shù)的簡陋期
經(jīng)歷4、5個(gè)月的半模塊化改造的準(zhǔn)現(xiàn)代期
到現(xiàn)在能整合全局資源(僅限web靜態(tài)資源),隨意整合新技術(shù)的現(xiàn)代期(未實(shí)施)
為什么要不斷的去折騰,去改造?僅僅是為了跟上“現(xiàn)代”的步伐嗎?下面我將講述每個(gè)階段是如何無痛改造的,為什么要改造。
從簡陋期到準(zhǔn)現(xiàn)代期舉個(gè)例子,我們以前的代碼是這樣的
html頁面部分javascript部分
在common.js里,是我們的定義的通用函數(shù),比如一些特定組件的部分代碼如header或footer,或者是字符串處理,日期格式化的函數(shù)等等,這些函數(shù)都以對象或函數(shù)的形式暴露在全局作用域里,非常的冗雜和不安全,隨著代碼量的增加,容易導(dǎo)致覆蓋,出現(xiàn)難以預(yù)料的bug,還有一個(gè)致命的弱點(diǎn)就是無法按需加載資源,我哪怕只是用到了其中一個(gè)小小的常量,都需要引用整個(gè)文件,然后從全局作用域里拿。
// common.js var Header = { var1: "", var2: {}, fn1: function() { // some code }, fn2: function() { // some code } } function strReplace() { // some code } ...
// individual.js // 也許我們早已有覺悟 使用了自執(zhí)行匿名函數(shù)來防止全局變量的污染 (function() { // 這里我們需要用到commonjs的函數(shù) 常量等 var afterHandleStr = strReplace(str); // 也許我們忘記strReplace函數(shù)已存在全局作用域又或者換了一個(gè)人 // 來維護(hù)這個(gè)文件可能又會(huì)定義一個(gè)函數(shù)叫strReplace function strReplace() { // 那么此時(shí)根據(jù)javascript特性,原先的函數(shù)已經(jīng)被覆蓋了, // 上面的調(diào)用邏輯優(yōu)先從最近的作用域開始找,于是會(huì)執(zhí)行到這里 } ... }());
因?yàn)轫?xiàng)目是迭代開發(fā)的,功能一點(diǎn)點(diǎn)疊加上去,考慮到整個(gè)項(xiàng)目的生命周期,不至于到后期完全無法維護(hù),所以我們必須以優(yōu)雅的姿態(tài)去重構(gòu)整個(gè)項(xiàng)目的資源引用方式,那就是模塊化,一個(gè)模塊做一件事情,并暴露它對外提供的接口以供具象化的頁面來使用。比如header,footer,nav,sidebar,utils等等。前端的模塊化有倆個(gè)標(biāo)準(zhǔn),一個(gè)AMD(Asynchronous Module Definition),一個(gè)是CMD(Common Module Definition),前者是異步模塊定義,推崇依賴前置,后者是通用模塊定義,推崇依賴就近,AMD的代表框架有requirejs,CMD的代表框架有seajs,都是很優(yōu)秀的作品,這里對二者有詳細(xì)的介紹。最后我選擇了requirejs作為本次重構(gòu)的基礎(chǔ),其實(shí)就當(dāng)是的代碼來說,改造起來并沒有什么難度,就是需要細(xì)心,細(xì)心,細(xì)心,只需要將common.js這個(gè)通用模塊進(jìn)行拆分就好了,頁面只需要引入一個(gè)js文件,如下面這樣
...
data-main是我們的代碼主入口,src是requireJs的源碼。從文件引用來說,至少我們不必再關(guān)心每次使用一個(gè)插件都要手動(dòng)來加入一個(gè)script標(biāo)簽了,如何引用呢?我下面會(huì)介紹。
假如我們以前的代碼是這樣的
// common.js (function(){ var exportObj = { aa: "aa", bb: "bb" ... } var utils = { replaceStr: function() { } ... } // 放到全局作用域 window.exportObj = exportObj; window.utils = utils; }()); // individual。js (function(){ var aa = constants.aa; var bb = constants.bb; var tempStr = utils.replaceStr("tempStr"); }());
上面的代碼使用了兩個(gè)全局對象,constants和utils,那么改造后應(yīng)該是:
// constants.js // 如果它不依賴于其他模塊,就不必聲明依賴的數(shù)組 define( function() { var exportObj = { aa: "aa", bb: "bb" ... } // 返回我們要暴露出來的對象,不用再放到全局作用域 return exportObj; } ); // utils.js define( function() { // 返回我們要暴露出來的對象,不用再放到全局作用域 return { replaceStr: function() { } ... }; } ); // individual define( [ "constants", "utils" ], function( consts, utils ) { var aa = consts.aa; var bb = consts.bb; var tempStr = utils.replaceStr("tempStr"); } ); // 或者 define( [ "constants", "utils" ], function() { var consts = require("constants"); var utils = require("utils"); var aa = consts.aa; var bb = consts.bb; var tempStr = utils.replaceStr("tempStr"); } );
是不是感覺毫無挑戰(zhàn)性,對,這就是一個(gè)體力活,細(xì)心點(diǎn)就好了
我們不必?fù)?dān)心還需要手動(dòng)去改動(dòng)第三方插件,現(xiàn)在的主流插件基本都會(huì)UMD方式去適配,也就是兼容了AMD,CMD,所以只需要直接引用第三方插件就行了,不必再去html文件里手動(dòng)引用script標(biāo)簽了,其他具體實(shí)現(xiàn)細(xì)節(jié)和必備的配置可以參照requirejs官網(wǎng)的例子
等到改造完,也還沒有愉快的結(jié)束,我們的準(zhǔn)現(xiàn)代期增加了一個(gè)優(yōu)化環(huán)節(jié),官方提供了r.js這個(gè)優(yōu)化器來幫我們打包壓縮代碼(畢竟生產(chǎn)環(huán)境過多的請求數(shù)還是不被允許的),此時(shí)的改造才真的做到了模塊化,能優(yōu)化,從簡陋期無痛過渡到準(zhǔn)現(xiàn)代期。此時(shí)的代碼,其實(shí)已經(jīng)具備了進(jìn)入現(xiàn)代期的要求,那就是規(guī)范模塊化。下面是我們即將進(jìn)行的改造,順利過渡到現(xiàn)代期,從而擁抱你想使用的新技術(shù)
從準(zhǔn)現(xiàn)代期到現(xiàn)代期其實(shí)這個(gè)階段,因?yàn)閷σ恍┬鹿ぞ咝录夹g(shù)的不熟悉,繞了很多彎子,花費(fèi)了不少的精力,好在弄出來了,基于webpack構(gòu)建工具,解放鍵盤F5,加入代碼風(fēng)格和規(guī)范的檢查工具,加入ECMAScript 6語法轉(zhuǎn)換工具等等,為什么要使用這些,概括為主要以下幾點(diǎn):
提升開發(fā)效率和代碼質(zhì)量
新語法新和技術(shù)能解決開發(fā)上的很多痛點(diǎn)和盲點(diǎn)
強(qiáng)大的整合性和包容性(相對于封閉的r.js優(yōu)化器終于可定制了)
emmmmmm思考中
首先我們介紹一下webapck是什么
這是webpack官方文檔首頁對其的簡單描述(ps: 其實(shí)中間的正方體是會(huì)旋轉(zhuǎn)的哦),強(qiáng)大的webpack能整合所有依賴的文件進(jìn)行處理,如less編譯(依賴less-loader),ES6語法轉(zhuǎn)換(依賴babel-loader),文件hash添加,自動(dòng)上傳ftp發(fā)布生產(chǎn)環(huán)境等等。還有就是webpack-dev-server這個(gè)開發(fā)神器,熱替換、自動(dòng)監(jiān)測文件變化刷新瀏覽器,雖然現(xiàn)代期并沒有用到實(shí)際項(xiàng)目中去,但是到現(xiàn)在(2017年7月22日),我已經(jīng)能完全拿出一套方案,使現(xiàn)有項(xiàng)目平滑過度到webpack。(ps:網(wǎng)上的教程大多是基于單頁單入口的SPA應(yīng)用,和后端完全解耦的,我們的項(xiàng)目是和后端處于半解耦狀態(tài),并且是多頁多入口,所以并不能使用大多數(shù)的webpack配置文件,需要進(jìn)行變通處理)
我們先來說說處理一般的SPA應(yīng)用的配置參數(shù)
module.exports = { entry: { // webpack生成的文件名和入口文件 // 我們是多頁入口,先以首頁為例 index: "./Public/dev/page/main.js" }, output: { path: config.build.assetsRoot, filename: "[name].js", }, module: { rules: [ { // 各種loaders } ] }, plugins: [ new HtmlWebpackPlugin( { // 模板生成后的文件名(可以加上路徑) filename: "index.html", // 入口文件的模板(也就是承載你頁面視圖的地方) template: "index.html", inject: true }) ] }
拋開其他雜七雜八配置不談,上面的配置就是大多數(shù)的SPA應(yīng)用的配置。用在我們的項(xiàng)目里,在根目錄運(yùn)行webpack會(huì)發(fā)現(xiàn)發(fā)生錯(cuò)誤,并提示缺少很多的模塊,因?yàn)檫@些我們自定義的模塊webpack本身并不能識別,所以這里有至關(guān)重要的一步,將現(xiàn)有的requirejs的配置文件里的paths同步遷移到webpack的配置文件里
// 在requirejs配置文件里可能是這樣寫的 require.config( { paths: { header: "./modules/header", } } ); // 那么我們就應(yīng)該將此配置交給webpack resolve: { alias: { header: "modules/header" // 路徑可能不一定是這個(gè) } }
然后我們再打包,運(yùn)行,發(fā)現(xiàn)丫的居然會(huì)報(bào)錯(cuò)了?最明顯的錯(cuò)誤就是define is not defined。讓我們來翻翻上面我們準(zhǔn)現(xiàn)代期的代碼
// individual define( [ "constants", "utils" ], function( consts, utils ) { var aa = consts.aa; var bb = consts.bb; var tempStr = utils.replaceStr("tempStr"); } );
這里的define就是報(bào)錯(cuò)的原因(webpack有時(shí)候并不能識別這里,有時(shí)候卻又能正確轉(zhuǎn)換成能運(yùn)行的代碼,沒有深究這里的原因,雖然webpack2已經(jīng)支持AMD風(fēng)格的代碼打包,但是我還是決定對這里稍作修改,變成CMD風(fēng)格,即使是使用CMD風(fēng)格的seajs依然是需要去掉外面那層包裹的函數(shù)的,不管怎樣都得改),于是我們只需要將上面的代碼調(diào)整為:
2017年7月24日22點(diǎn)50分更新,經(jīng)過我的嘗試,只要配置依賴都正確,完全可以直接打包,不用非得改成CMD,于是換成webpack更輕松了~~?
var consts = require("constants"); var utils = require("utils"); var aa = consts.aa; var bb = consts.bb; var tempStr = utils.replaceStr("tempStr"); // 如果這里有return的話需要將return obj調(diào)整為 module.exports = obj;
至此我們再打包便可以輕輕松松合并了(當(dāng)然如果你要提取公共代碼的話又是另外一個(gè)插件了,這里不再贅述)
打包發(fā)布的問題解決了,最重要的一環(huán)開發(fā)環(huán)境的搭建呢?
其實(shí)機(jī)智的我早料到這種配置在我們的項(xiàng)目并不完美,因?yàn)?b>HtmlWebpackPlugin這個(gè)插件需要的模板是放在硬盤里的靜態(tài)文件模板,它會(huì)自動(dòng)插入構(gòu)建好的js和css文件,我們的模板不是靜態(tài)的,是從php后端渲染的一段動(dòng)態(tài)的html,還是作死試了試,果然出現(xiàn)了以下情況
動(dòng)態(tài)引用的header、footer不見了
頁面出現(xiàn)一堆后端模板的語法{$xxx}{$yyy}{$zzz}
其實(shí)webpack-dev-server提供了一個(gè)代理功能,那這里的問題解決起來就美滋滋了。單純的我最先的配置是這樣的:
var express = require( "express" ) var proxyMiddleware = require( "http-proxy-middleware" ) var app = express(); // 這是代理的 var proxyTable = { "/": { target: "http://xxxx.cn/" } } Object.keys( proxyTable ).forEach( function( context ) { var options = proxyTable[ context ] if ( typeof options === "string" ) { options = { target: options } } // 應(yīng)用代理地址和代理目標(biāo) app.use( proxyMiddleware( options.filter || context, options ) ) } )
以上代碼將我們所有的請求路徑一股腦全部代理給后端php服務(wù)了,HtmlWebpackPlugin這個(gè)插件會(huì)自動(dòng)寫入依賴的腳本文件和樣式表文件,但是此時(shí)的文件是webpack-dev-server服務(wù)生成的,并且存在于內(nèi)存里,所以此時(shí)我們再運(yùn)行webpack開啟的服務(wù),就會(huì)造成頁面出來了(包括任何動(dòng)態(tài)從服務(wù)端渲染的數(shù)據(jù)),但是樣式和js都沒有加載,因?yàn)檎埱蟊淮淼搅撕蠖?,后端的目錄里并不存在這些文件(廢話么),所以我們需要過濾掉這些特定的請求不讓http-proxy-middleware插件進(jìn)行代理,為了區(qū)分這些特定的請求,我們將entry字段里的文件名都加上一個(gè)前綴__webpack或任何獨(dú)一無二的與后臺請求開頭不一樣的字符串,此時(shí)proxyTable里的filter函數(shù)就派上用場了,查看官方文檔是這么描述這個(gè)函數(shù)的
For full control you can provide a custom function to determine which requests should be proxied or not.
為了完全控制你的請求,你可以定義一個(gè)函數(shù)來確定這些請求是否應(yīng)該被提交
于是我終于拿出一個(gè)滿意的代理配置文件,開心得我仿佛升職加薪了一樣?
var proxyTable = { "/": { target: "http://xxx.cn/", changeOrigin: true, filter( pathname, req ) { return !new RegExp( `^/(__webpack|${assetsSubDirectory})` ).test( pathname ); } } }
讓我來解釋一下上面的代碼:未匹配到以__webpack開頭的請求,都進(jìn)行代理,這里添加了一個(gè)assetsSubDirectory變量,這個(gè)變量其實(shí)是webpack生成的圖片、字體文件、json文件、svg等仍然存在于內(nèi)存里的引用的路徑,因?yàn)樵趦?nèi)存里隨著我們的編碼可能實(shí)時(shí)變動(dòng),所以它們還是不需要做代理,直接過濾掉。
對了,遺漏了一個(gè)很重要的配置,代碼如下:
plugins: [ new HtmlWebpackPlugin( { alwaysWriteToDisk: true, // php端使用到的模板 filename: `${ROOT}/Application/Home/View/Index/index.html`, // 模板文件 template: `${ROOT}/Application/Home/View-template/Index/index.html`, chunks: [ "__webpack-indexController" ], inject: true }) ]
機(jī)智的我們肯定能發(fā)現(xiàn)View-template這里的不同,見名知意,這個(gè)文件夾里的html都是對應(yīng)的后端的模板視圖文件,我們通過alwaysWriteToDisk這個(gè)參數(shù)(其實(shí)還需要配合另外一個(gè)插件)以template字段的值為目標(biāo),實(shí)時(shí)寫入到filename對應(yīng)的文件里,而此時(shí),因?yàn)闉g覽器訪問的頁面里因?yàn)槲覀儐?dòng)webpack-dev-server時(shí)已經(jīng)編譯了這個(gè)文件,js會(huì)主動(dòng)和webpack服務(wù)建立一個(gè)eventSource長連接(這個(gè)連接也是排除在代理范圍內(nèi)的)來監(jiān)聽文件變化,所以就會(huì)自動(dòng)刷新瀏覽器,從而實(shí)現(xiàn)我們的live-reload。
至此,從準(zhǔn)現(xiàn)代期到現(xiàn)代期的過渡方案就算是完成了,接下面便是尋找一個(gè)合適的時(shí)間點(diǎn)實(shí)施到項(xiàng)目中去。若你要問我那么多頁面是不是全都一個(gè)個(gè)得配,當(dāng)然是,但是為了方便易維護(hù),能不侵入現(xiàn)有項(xiàng)目去修改文件名,我們肯定需要去手動(dòng)編寫一個(gè)map映射文件,來指明我們的模板文件對應(yīng)的入口文件,通過這個(gè)map我們再來動(dòng)態(tài)生成entry和HtmlWebpackPlugin需要的模板路徑,當(dāng)然這里并不是沒有便捷的辦法,我們可以寫一個(gè)腳本去讀取View-template下面的目錄來自動(dòng)生成map但是因?yàn)槲覀兺诿臅r(shí)候文件夾和對應(yīng)的入口文件并不能對應(yīng)上,就得修改,這并不是推薦的做法,而且也不方便我們在改造代碼風(fēng)格的時(shí)候進(jìn)行單個(gè)調(diào)試。
上面的示例代碼都不是完整的,因?yàn)槲也⒉皇且峁┮粋€(gè)webpack的教程,而是解決后端和前端html耦合的webpack-dev-server配置的問題。
以上內(nèi)容都轉(zhuǎn)自我自己的博客原文地址
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/84491.html
摘要:前端日報(bào)精選中的組件通信問題詳解頁面的渲染過程面試中問什么問題加實(shí)現(xiàn)圖片前端壓縮并上傳用畫一個(gè)迷宮中文譯當(dāng)不使用框架時(shí)瘋狂的技術(shù)宅在翻譯面向編程連續(xù)改造個(gè)網(wǎng)頁掘金周刊技術(shù)周刊期知乎專欄技術(shù)周刊包管理的前世今生眾成翻譯版發(fā)布 2017-07-31 前端日報(bào) 精選 React中的組件通信問題詳解 Weex 頁面的渲染過程面試中問什么React問題?HTML5 file API加canvas...
摘要:前端的發(fā)展歷程什么是前端前端針對瀏覽器的開發(fā),代碼在瀏覽器運(yùn)行后端針對服務(wù)器的開發(fā),代碼在服務(wù)器運(yùn)行前端三劍客超文本標(biāo)記語言是構(gòu)成世界的基石。 前端的發(fā)展歷程 什么是前端 前端:針對瀏覽器的開發(fā),代碼在瀏覽器運(yùn)行 后端:針對服務(wù)器的開發(fā),代碼在服務(wù)器運(yùn)行 前端三劍客 HTML CSS JavaScript HTML HTML(超文本標(biāo)記語言——HyperText Markup ...
摘要:打包出來的代碼快照如下,注意看注釋中的時(shí)序?qū)嶋H上,的處理同相差無幾,只是在定義模塊和引入模塊時(shí)會(huì)去處理標(biāo)識,從而兼容其在語法上的差異。 前言 隨著 Web 技術(shù)的蓬勃發(fā)展和依賴的基礎(chǔ)設(shè)施日益完善,前端領(lǐng)域逐漸從瀏覽器擴(kuò)展至服務(wù)端(Node.js),桌面端(PC、Android、iOS),乃至于物聯(lián)網(wǎng)設(shè)備(IoT),其中 JavaScript 承載著這些應(yīng)用程序的核心部分,隨著其規(guī)模化和...
摘要:前言前端模塊化,主要是解決兩個(gè)問題命名空間沖突,文件依賴管理。目前解決的方法是模塊化命名空間各個(gè)模塊的命名空間獨(dú)立。模塊化構(gòu)建工具,等是用來組織前端模塊的構(gòu)建工具加載器。 前言 前端模塊化,主要是解決兩個(gè)問題——命名空間沖突,文件依賴管理。 坑___命名空間沖突 我自己測試好的代碼和大家合并后怎么起沖突了? 頁面腳本的變量或函數(shù)覆蓋了公有腳本的。 坑___文件依賴管理 明明項(xiàng)目需...
閱讀 1177·2023-04-26 02:02
閱讀 2474·2021-09-26 10:11
閱讀 3622·2019-08-30 13:10
閱讀 3818·2019-08-29 17:12
閱讀 782·2019-08-29 14:20
閱讀 2252·2019-08-28 18:19
閱讀 2301·2019-08-26 13:52
閱讀 1023·2019-08-26 13:43