在項目的前端開發中,對於絕大多數的小夥伴來講,固然,也包括我,不可避免的須要在項目中使用到一些第三方的組件包。這時,團隊中的小夥伴是選擇直接去組件的官網上下載,仍是圖省事直接在網上搜索,而後從一些來源不明的地方下載,咱們就沒法管控了。同時,咱們添加的組件間可能存在各類依賴關係,若是咱們沒有正確下載引用的話,到最後可能仍是沒法正常使用。css
所以,如何從可信的源下載組件包,以及如何輕鬆的解決各個組件間的依賴關係就成了咱們須要解決的問題,那麼,有沒有一種工具能夠幫咱們解決這一問題?你好,有的,npm 瞭解一下。前端
代碼倉儲:https://github.com/Lanesra712/grapefruit-commonnode
在 .NET Framework 的項目中,咱們能夠在項目中經過 Nuget 下載安裝前端的組件包。可是 Nuget 更多的是做爲 .NET 後端項目中的包管理器,在這裏管理前端的組件包顯得有些不太合適。jquery
因而,在 .NET Core 的時代到來後,伴隨着前端的發展,微軟在建立的示例項目中開始推薦咱們使用 bower 來管理咱們項目中的前端組件包,而後,bower is dead。。。。git
因此這裏,我採用 npm 做爲咱們的 ASP.NET Core 項目中的前端包管理器。github
Node.js 是一個可以在服務端運行 Javascript 的執行環境,也就是說,Javascript 不只能夠用於前端,也能夠構建後端服務了。而 npm 則是 Node.js 官方提供的包管理工具,因此在使用 npm 以前,須要在咱們的電腦上安裝 Node.js 環境。web
固然,若是你以前有開發過 Vue、Angular 這類的前端項目,你確定已經安裝好了。若是沒有,打開 Node.js 的官網(https://nodejs.org/en/download),根據你正在使用的操做系統信息,選擇安裝包下載就能夠了。npm
若是你使用的是 window 系統,很簡單,下載 msi 安裝包,一路 next 便可。在最新版本的 Node.js 安裝包中,npm 是隨着 Node.js 的安裝一塊兒完成的。咱們可使用下面的命令進行驗證,當能夠打印出你安裝的版本信息,則說明安裝已經完成了。json
//一、node.js 版本 node -v //二、npm 版本 npm -v
這篇文章的示例項目,我採用的是 ASP.NET Core 2.2 默認生成的 MVC 項目,由於在寫文章的過程當中有過更換解決方案,因此文章中的截圖可能會出現名稱先後不對應的狀況,還請見諒。gulp
當示例項目建立完成後,會自動在項目中引用 bootstrap 和 jquery,因此,咱們就在這個項目的基礎上,嘗試採用 npm 來管理咱們的前端組件包。
右擊咱們的項目,添加一個 package.json 配置文件。在這個 json 文件中定義了這個項目所須要的各類前端模塊,以及項目的配置信息(好比名稱、版本、許可證等等)。當咱們從別處拷貝這個項目後,經過執行 npm install 命令,就會根據這個配置文件,自動下載項目中所須要引用的前端組件包。
打開 package.json 文件,若是你選擇使用 VS 進行編輯的話,能夠看到 VS 會自動幫咱們出現代碼補齊提示。這裏我添加了一個 dependencies 節點,它與 devDependencies 節點都表明咱們項目中須要安裝的插件。不一樣的是,devDependencies 裏面的插件只用於開發環境,不用於生產環境,而 dependencies 中引用的則是須要發佈到生產環境中的。
例如,這裏咱們須要在項目中添加 bootstrap 和 jquery,由於在正式發佈時若是缺乏這兩個組件,就會致使咱們的程序報錯,因此這裏咱們須要添加到 dependencies 節點下,而像後面咱們使用到的 gulp 的一系列插件,只有在咱們進行項目開發時纔會使用到,因此咱們只須要添加到 devDependencies 便可。
這裏我推薦使用命令行的方式添加組件,能夠更好地展現出咱們添加的組件須要添加哪些依賴。右鍵選中咱們的示例項目,選擇 Open Command Line,打開控制檯,輸入下列的命令,將 bootstrap 添加到咱們的項目中。
在 install 命令中咱們添加了 --save 修飾,表示須要將 bootstrap 添加到 dependencies 節點下面。若是,你須要將引用到的 package 安裝到 devDependencies 節點下,則須要使用 --save-dev 修飾。
npm install bootstrap --save
能夠看到,安裝完成後,npm 提示咱們 bootstrap 依賴於 jquery 和 popper.js,因此這裏咱們手動添加上這兩個依賴的組件。
當咱們安裝 jquery 的 1.9.1 版本後,由於以前的 jquery 版本存在一些安全隱患,因此 npm 會提示咱們執行 npm audit 命令來查看當前項目中可能存在的安全隱患,以及對於如何解決這些隱患的建議。
這裏我進行了版本升級,你能夠根據本身的需求進行操做。請特別注意,當你在完成項目的基礎包加載後,後續對於包版本的升級必定要謹慎、謹慎、再謹慎。升級完成後的 package.json 文件以下所示。
{ "version": "1.0.0", "name": "aspnetcore.npm.tutorial", "private": true, "devDependencies": {}, "dependencies": { "bootstrap": "^4.3.1", "jquery": "^3.4.1", "popper.js": "^1.14.7" } }
在咱們第一次執行 npm install 命令時,系統自動爲咱們建立了 package-lock.json 這個文件,用來記錄當前狀態下實際安裝的各個 npm package 的具體來源和版本號,當前項目下的 package-lock.json 文件以下。
{ "name": "aspnetcore.npm.tutorial", "version": "1.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { "bootstrap": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.3.1.tgz", "integrity": "sha512-rXqOmH1VilAt2DyPzluTi2blhk17bO7ef+zLLPlWvG494pDxcM234pJ8wTc/6R40UWizAIIMgxjvxZg5kmsbag==" }, "jquery": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.4.1.tgz", "integrity": "sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw==" }, "popper.js": { "version": "1.14.7", "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.14.7.tgz", "integrity": "sha512-4q1hNvoUre/8srWsH7hnoSJ5xVmIL4qgz+s4qf2TnJIMyZFUFMGH+9vE7mXynAlHSZ/NdTmmow86muD0myUkVQ==" } } }
那麼 package-lock.json 這個文件到底有什麼用呢?
由於咱們在 npm 上下載的包遵循了大版本.次要版本.小版本的版本定義。例如,在上面的示例中,咱們使用 npm install 命令安裝的 bootstrap 版本爲 4.3.1,而在安裝插件包的時候,package.json 通常指定的是包的範圍,即只對插件包的大版本進行限定。所以,當別人拷貝了你的代碼,準備還原引用的包時,可能此時的 bootstrap 已經有 4.4.4 版本的了,這時,若是你使用了某些 4.3.1 版本中的特性,而在 4.4.4 版本中已經被移除的話,毫無疑問,你的代碼就會出 bug。
而當項目中存在了 package-lock.json 文件以後,由於項目中引用的組件包版本和來源信息已經鎖定在了這個文件中了,此時,當別人拷貝了代碼,準備還原時,就能夠準確的加載到你開發時使用的組件版本。固然,若是你修改了引用的包信息,當執行 npm install 命令時,package-lock.json 文件會同步更新。
對於包的版本限定條件以下所示。
指定版本:好比此例中 bootstrap 的版本爲 4.3.1,當從新安裝時只安裝指定的 4.3.1 版本。
波浪號(tilde) + 指定版本:好比 ~1.2.2,表示安裝1.2.x 的最新版本(不低於1.2.2),可是不安裝 1.3.x,也就是說安裝時不改變大版本號和次要版本號。
插入號(caret) + 指定版本:好比 ˆ1.2.2,表示安裝1.x.x 的最新版本(不低於1.2.2),可是不安裝 2.x.x,也就是說安裝時不改變大版本號。須要注意的是,若是大版本號爲0,則插入號的行爲與波浪號相同。
latest:始終安裝包的最新版本。
當咱們經過 npm 添加好須要使用的組件包後,就須要考慮如何在項目中使用。
咱們知道,在 ASP.NET Core 項目中,對於 web 項目中的靜態文件的獲取,一般是使用 StaticFileMiddleware 這個中間件。而 「{contentroot}/wwwroot」 這個目錄是對外發布項目中的靜態文件默認使用的根目錄,也就是說,咱們須要將使用到的 npm 包移動到 wwwroot 文件下。
手動複製?em,工做量彷佛有點大。
不過,既然這裏咱們使用到了 node.js,那麼這裏就可使用 gulp.js 這個自動化任務執行器來幫咱們實現這一功能,固然,你也能夠根據本身的習慣使用別的工具。
經過使用 gulp.js,咱們就能夠自動的執行移動文件,打包壓縮 js、css、image、刪除文件等等,幫咱們省了再經過 bundle 去打包壓縮 css 和 js 文件的過程。
在項目中使用 gulp.js 的前提,須要咱們做爲項目的開發依賴(devDependencies)安裝 gulp 和一些用到的 gulp 插件,由於會下載不少的東西,整個安裝的過程長短依據你的網絡狀況而定,嗯,請坐和放寬。
在這個項目中使用到的 gulp 插件以下所示,若是你須要拷貝下面的命令行的話,在執行時請刪除註釋內容。
//gulp.js npm install gulp --save-dev //壓縮 css npm install gulp-clean-css --save-dev //合併文件 npm install gulp-concat --save-dev //壓縮 js npm install gulp-uglify --save-dev //重命名 npm install gulp-rename --save-dev //刪除文件、文件夾 npm install rimraf --save-dev //監聽文件變化 npm install gulp-changed --save-dev
安裝完成後的 package.json 文件以下所示。
{ "version": "1.0.0", "name": "aspnetcore.npm.tutorial", "private": true, "devDependencies": { "gulp": "^4.0.1", "gulp-changed": "^3.2.0", "gulp-clean-css": "^4.2.0", "gulp-concat": "^2.6.1", "gulp-rename": "^1.4.0", "gulp-uglify": "^3.0.2", "rimraf": "^2.6.3" }, "dependencies": { "bootstrap": "^4.3.1", "jquery": "^3.4.1", "popper.js": "^1.14.7" } }
當咱們安裝好全部的 gulp 組件包以後,在咱們的項目根路徑下建立一個 gulpfile.js 文件,文件的內容以下所示。
/// <binding BeforeBuild='min' Clean='clean' ProjectOpened='auto' /> "use strict"; //加載使用到的 gulp 插件 const gulp = require("gulp"), rimraf = require("rimraf"), concat = require("gulp-concat"), cssmin = require("gulp-clean-css"), rename = require("gulp-rename"), uglify = require("gulp-uglify"), changed = require("gulp-changed"); //定義 wwwroot 下的各文件存放路徑 const paths = { root: "./wwwroot/", css: './wwwroot/css/', js: './wwwroot/js/', lib: './wwwroot/lib/' }; //css paths.cssDist = paths.css + "**/*.css";//匹配全部 css 的文件所在路徑 paths.minCssDist = paths.css + "**/*.min.css";//匹配全部 css 對應壓縮後的文件所在路徑 paths.concatCssDist = paths.css + "app.min.css";//將全部的 css 壓縮到一個 css 文件後的路徑 //js paths.jsDist = paths.js + "**/*.js";//匹配全部 js 的文件所在路徑 paths.minJsDist = paths.js + "**/*.min.js";//匹配全部 js 對應壓縮後的文件所在路徑 paths.concatJsDist = paths.js + "app.min.js";//將全部的 js 壓縮到一個 js 文件後的路徑 //使用 npm 下載的前端組件包 const libs = [ { name: "jquery", dist: "./node_modules/jquery/dist/**/*.*" }, { name: "popper", dist: "./node_modules/popper.js/dist/**/*.*" }, { name: "bootstrap", dist: "./node_modules/bootstrap/dist/**/*.*" }, ]; //清除壓縮後的文件 gulp.task("clean:css", done => rimraf(paths.minCssDist, done)); gulp.task("clean:js", done => rimraf(paths.minJsDist, done)); gulp.task("clean", gulp.series(["clean:js", "clean:css"])); //移動 npm 下載的前端組件包到 wwwroot 路徑下 gulp.task("move", done => { libs.forEach(function (item) { gulp.src(item.dist) .pipe(gulp.dest(paths.lib + item.name + "/dist")); }); done() }); //每個 css 文件壓縮到對應的 min.css gulp.task("min:css", () => { return gulp.src([paths.cssDist, "!" + paths.minCssDist], { base: "." }) .pipe(rename({ suffix: '.min' })) .pipe(changed('.')) .pipe(cssmin()) .pipe(gulp.dest('.')); }); //將全部的 css 文件合併打包壓縮到 app.min.css 中 gulp.task("concatmin:css", () => { return gulp.src([paths.cssDist, "!" + paths.minCssDist], { base: "." }) .pipe(concat(paths.concatCssDist)) .pipe(changed('.')) .pipe(cssmin()) .pipe(gulp.dest(".")); }); //每個 js 文件壓縮到對應的 min.js gulp.task("min:js", () => { return gulp.src([paths.jsDist, "!" + paths.minJsDist], { base: "." }) .pipe(rename({ suffix: '.min' })) .pipe(changed('.')) .pipe(uglify()) .pipe(gulp.dest('.')); }); //將全部的 js 文件合併打包壓縮到 app.min.js 中 gulp.task("concatmin:js", () => { return gulp.src([paths.jsDist, "!" + paths.minJsDist], { base: "." }) .pipe(concat(paths.concatJsDist)) .pipe(changed('.')) .pipe(uglify()) .pipe(gulp.dest(".")); }); gulp.task("min", gulp.series(["min:js", "min:css"])); gulp.task("concatmin", gulp.series(["concatmin:js", "concatmin:css"])); //監聽文件變化後自動執行 gulp.task("auto", () => { gulp.watch(paths.css, gulp.series(["min:css", "concatmin:css"])); gulp.watch(paths.js, gulp.series(["min:js", "concatmin:js"])); });
在 gulp.js 中主要有四個 API,就像咱們項目中的 gulpfile 更多的是對於第三方插件的使用,而咱們只須要經過 pipe 將任務中的每一步操做添加到任務隊列中便可。完整的 API 文檔,你們能夠去官網去詳細查看 => https://gulpjs.com/docs/en/api/concepts
gulp.src:根據匹配、或是路徑加載文件;
gulp.dest:輸出文件到指定路徑;
gulp.task:定義一個任務;
gulp.watch:監聽文件變化。
當咱們建立好任務後,刪除 wwwroot 路徑下的引用的第三方組件包,運行咱們的示例項目,毫無疑問,整個頁面的樣式都已經丟失了。
選中 gulpfile.js,右鍵打開任務運行程序資源管理器。能夠看到,系統會自動顯示出咱們定義的全部任務,這時,咱們能夠鼠標右鍵點擊任務,選中運行,便可執行咱們的任務。
然而,咱們手動去執行彷佛有些不智能,咱們能不能自動執行某些任務呢?答案固然是能夠,一樣是鼠標右鍵點擊任務,點擊綁定菜單選項,咱們就將定義好的任務綁定事件上。
例如,在個人 gulpfile 中,我綁定了三個事件:生成解決方案前執行 min task,清理解決方案時執行 clean task,打開項目時執行 auto task,而 VS 也自動幫咱們生成了以下的綁定腳本到咱們的 gulpfile 上。
/// <binding BeforeBuild='min' Clean='clean' ProjectOpened='auto' />
經過將綁定事件與 gulp API 進行結合,就能夠很好的實現咱們的需求。就像這裏,我在項目打開時綁定了自動監聽文件變化的任務,這時,只要我修改了 css、js 文件,gulp 就會自動幫咱們實現對於文件的壓縮。
PS:若是你將任務綁定到項目打開的事件上,則是須要下一次打開項目時才能自動執行。
這一章主要是介紹瞭如何在咱們的 ASP.NET Core 項目中經過 npm 管理咱們的前端組件包,同時,使用 gulp 去執行一些移動文件、壓縮文件的任務。隨着這些年前端的發展,前端的開發愈來愈規範化,也愈來愈朝後端靠攏了,咱們做爲傳統意義上的後端程序猿,在涉及到前端的開發時,若是能夠用到這些能夠規範化咱們的前端項目的特性,仍是極好的。由於本身水平也很菜,不少東西並無很詳細的涉及到,可能還須要你在實際使用中進行進一步的探究,畢竟,實踐出真知。