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