摘要:我們已經(jīng)運用了的一些閃亮的新特性,那么如何才能轉(zhuǎn)化為的代碼呢首先,我們需要通過來安裝在全局安裝會提供我們一個命令行工具。
你是不是也在為可以使用ES6的新特性而興奮,卻不太確定應(yīng)該從哪開始,或者如何開始?不止你一個人這樣!我已經(jīng)花了一年半的時間去解決這個幸福的難題。在這段時間里 JavaScript 工具鏈中有幾個令人興奮的突破。
這些突破讓我們可以用ES6書寫完全的JS模塊,而不會為了一些基本的條件而妥協(xié),比如testing,linting 和(最重要的)其他人可以輕易理解我們所寫的代碼。
在這篇文章中,我們集中精力在如何用ES6構(gòu)建JS模塊,并且無論你在你的網(wǎng)站或者app中使用CommonJS,AMD(asynchronous module definition)或者普通的網(wǎng)頁script引入,這個模塊都可以輕易被引用。
The Tools在這個系列文章的第一部分和第二部分,我們來看一下這些卓越的工具們。在這篇文章中,我們詳細說明如何編寫,編譯,打包代碼;而在第二篇文章會集中在linting,formatting 和 testing(利用 JSCS,ESLint,mocha,Chai,Karma 和 Istanbul)。讓我們來看看在這篇文章中涉及到的工具:
Babel(剛剛度過了它的第一個生日)可以把ES6代碼轉(zhuǎn)化為ES5代碼,不僅簡單,而且優(yōu)雅。
Webpack,webpack平寂了我們組里的“模塊戰(zhàn)爭”,我們每個人都鎮(zhèn)定得使用著webpack來應(yīng)付_一切_(CommonJS,AMD 和 ES6)。它也在打包獨立的ES6庫方面做得非常棒——這是我們在過去一直渴望看到的。
Gulp一個強大的自動化構(gòu)建工具。
The Goal WRITE IN ES6, USE IN ES5我們將要討論的是書寫客戶端(client-side)ES6 _libraries_,而不是整個網(wǎng)站或者 app 。(無論是在你的開源項目里或者是在你工作中的軟件項目,這是可以在不同的項目中可復(fù)用的代碼。)”等一下!“,你可能會想:”這個難道不是在瀏覽器支持ES6之后才能實現(xiàn)的嗎?“
你是對的!然而,我們利用上面提到的Babel可以把ES6代碼轉(zhuǎn)化為ES5代碼,在大多數(shù)情況下現(xiàn)在就可以實現(xiàn)我們的目標。
MAKE IT EASY FOR ANYONE TO CONSUME我們目標的第二部分是寫一個無論在什么模塊規(guī)范下都可以使用的JS模塊。AMD死忠飯?你會得到一個可用的模塊。CommonJS 加 browserify 才是你的最愛?沒問題!你會得到一個可用的模塊。或者你對AMD和CommonJS不感冒,你只是想要在你的頁面上加一個引用并且成功運行?你也會得到一個可用的模塊。Webpack會把我們的代碼打包成UMD( universal module definition)模塊規(guī)范,使我們的代碼在任何代碼規(guī)范中都可用。
Setting Up Our Project在接下來的幾分鐘,我們將要完成這些代碼。我經(jīng)常用src/,spec/ 和 lib/文件夾來構(gòu)建項目。在src/目錄里,你會看到一個有趣的示例模塊,這個模塊是提供樂高電影里的樂高角色的隨機語錄。這個示例會用到ES6的classes,modules,const,destructuring,generator等--這些可以被安全轉(zhuǎn)化為ES5代碼的新特性。
這篇文章的主要目的是討論如何利用 Babel 和 Webpack 來編譯和打包 ES6 library。然而我還是想簡要的介紹我們的示例代碼以證明我們切實在用 ES6。
Note: 你如果是 ES6 新手,不必擔心。這個示例足夠簡單到你們會看懂。
The LegoCharacter Class在 LegoCharacter.js 模塊中,我們可以看到如下代碼(查看注釋了解更多):
// LegoCharacter.js // Let"s import only the getRandom method from utils.js import { getRandom } from "./utils"; // the LegoCharacter class is the default export of the module, similar // in concept to how many node module authors would export a single value export default class LegoCharacter { // We use destructuring to match properties on the object // passed into separate variables for character and actor constructor( { character, actor } ) { this.actor = actor; this.name = character; this.sayings = [ "I haven"t been given any funny quotes yet." ]; } // shorthand method syntax, FOR THE WIN // I"ve been making this typo for years, it"s finally valid syntax :) saySomething() { return this.sayings[ getRandom( 0, this.sayings.length - 1 ) ]; } }
這些代碼本身很無聊--class意味著可以被繼承,就像我們在 Emmet.js 模塊里做的:
// emmet.js import LegoCharacter from "./LegoCharacter"; // Here we use the extends keyword to make // Emmet inherit from LegoCharacter export default class Emmet extends LegoCharacter { constructor() { // super lets us call the LegoCharacter"s constructor super( { actor: "Chris Pratt", character: "Emmet" } ); this.sayings = [ "Introducing the double-decker couch!", "So everyone can watch TV together and be buddies!", "We"re going to crash into the sun!", "Hey, Abraham Lincoln, you bring your space chair right back!", "Overpriced coffee! Yes!" ]; } }
在我們的項目中,LegoCharacter.js 和 emmet.js 都是分開的多帶帶的文件--這是我們示例代碼中的典型例子。跟你之前寫的 JavaScript 代碼相比,我們的示例代碼可能比較陌生。然而,在我們完成我們一系列的工作之后,我們將會得到一個 將這些代碼打包到一起的‘built’版本。
The index.js我們項目中的另一個文件-- index.js --是我們項目的主入口。在這個文件中 import 了一些 Lego 角色的類,生成他們的實例,并且提供了一個生成器函數(shù)(generator function),這個生成器函數(shù)來 yield 一個隨機的語錄:
// index.js // Notice that lodash isn"t being imported via a relative path // but all the other modules are. More on that in a bit :) import _ from "lodash"; import Emmet from "./emmet"; import Wyldstyle from "./wyldstyle"; import Benny from "./benny"; import { getRandom } from "./utils"; // Taking advantage of new scope controls in ES6 // once a const is assigned, the reference cannot change. // Of course, transpiling to ES5, this becomes a var, but // a linter that understands ES6 can warn you if you // attempt to re-assign a const value, which is useful. const emmet = new Emmet(); const wyldstyle = new Wyldstyle(); const benny = new Benny(); const characters = { emmet, wyldstyle, benny }; // Pointless generator function that picks a random character // and asks for a random quote and then yields it to the caller function* randomQuote() { const chars = _.values( characters ); const character = chars[ getRandom( 0, chars.length - 1 ) ]; yield `${character.name}: ${character.saySomething()}`; } // Using object literal shorthand syntax, FTW export default { characters, getRandomQuote() { return randomQuote().next().value; } };
在這個代碼塊中,index.js 引入了lodash,我們的三個Lego角色的類,和一個實用函數(shù)(utility function)。然后生成三個類的實例,導(dǎo)出(exports)這三個實例和getRandomQuote方法。一切都很完美,當代碼被轉(zhuǎn)化為ES5代碼后依然會有一樣的作用。
OK. Now What?我們已經(jīng)運用了ES6的一些閃亮的新特性,那么如何才能轉(zhuǎn)化為ES5的代碼呢?首先,我們需要通過 npm來安裝Babel:
npm install -g babel
在全局安裝Babel會提供我們一個babel 命令行工具(command line interface (CLI) option)。如果在項目的根目錄寫下如下命令,我們可以編譯我們的模塊代碼為ES5代碼,并且把他們放到lib/目錄:
babel ./src -d ./lib/
現(xiàn)在看一下lib/目錄,我們將看到如下文件列表:
LegoCharacter.js benny.js emmet.js index.js utils.js wyldstyle.js
還記得上面我們提到的嗎?Babel把每一個模塊代碼轉(zhuǎn)化為ES5代碼,并且以同樣的目錄結(jié)構(gòu)放入lib/目錄。看一下這些文件可以告訴我們兩個事情:
首先,在node環(huán)境中只要依賴 babel/register運行時,這些文件就可以馬上使用。在這篇文章結(jié)束之前,你會看到一個在node中運行的例子。
第二,我們還有很多工作要做,以使這些文件打包進一個文件中,并且以UMD(universal module definition )規(guī)范打包,并且可以在瀏覽器環(huán)境中使用。
Enter webpack我打賭你已經(jīng)聽說過Webpack,它被描述為“一個JavaScript和其他靜態(tài)資源打包工具”。Webpack的典型應(yīng)用場景就是作為你的網(wǎng)站應(yīng)用的加載器和打包器,可以打包你的JavaScript代碼和其他靜態(tài)資源,比如CSS文件和模板文件,將它們打包為一個(或者更多)文件。webpack有一個非常棒的生態(tài)系統(tǒng),叫做“l(fā)oaders”,它可以使webpack對你的代碼進行一些變換。打包一個UMD規(guī)范的文件并不是webpack最用途廣泛的應(yīng)用,我們還可以用webpack loader將ES6代碼轉(zhuǎn)化為ES5代碼,并且把我們的示例代碼打包為一個輸出文件。
LOADERS在webpack中,loaders可以做很多事情,比如轉(zhuǎn)化ES6代碼為ES5,把LESS編譯為CSS,加載JSON文件,加載模板文件,等等。Loaders為將要轉(zhuǎn)化的文件一個test模式。很多l(xiāng)oaders也有自己額外的配置信息。(好奇有多少loaders存在?看這個列表)
我們首先在全局環(huán)境安裝webpack(它將給我們一個webpack命令行工具(CLI)):
npm install -g webpack
接下來為我們本地項目安裝babel-loader。這個loader可以加載我們的ES6模塊并且把它們轉(zhuǎn)化為ES5。我們可以在開發(fā)模式安裝它,它將出現(xiàn)在package.json文件的devDependencies中:
npm install --save-dev babel-loader
在我們開始使用webpack之前,我們需要生成一個webpack的配置文件,以告訴webpack我們希望它對我們的文件做些什么工作。這個文件經(jīng)常被命名為webpack.config.js,它是一個node模塊格式的文件,輸出一系列我們需要webpack怎么做的配置信息。
下面是初始化的webpack.config.js,我已經(jīng)做了很多注釋,我們也會討論一些重要的細節(jié):
module.exports = { // entry is the "main" source file we want to include/import entry: "./src/index.js", // output tells webpack where to put the bundle it creates output: { // in the case of a "plain global browser library", this // will be used as the reference to our module that is // hung off of the window object. library: "legoQuotes", // We want webpack to build a UMD wrapper for our module libraryTarget: "umd", // the destination file name filename: "lib/legoQuotes.js" }, // externals let you tell webpack about external dependencies // that shouldn"t be resolved by webpack. externals: [ { // We"re not only webpack that lodash should be an // external dependency, but we"re also specifying how // lodash should be loaded in different scenarios // (more on that below) lodash: { root: "_", commonjs: "lodash", commonjs2: "lodash", amd: "lodash" } } ], module: { loaders: [ // babel loader, testing for files that have a .js extension // (except for files in our node_modules folder!). { test: /.js$/, exclude: /node_modules/, loader: "babel", query: { compact: false // because I want readable output } } ] } };
讓我們來看一些關(guān)鍵的配置信息。
Output一個wenpack的配置文件應(yīng)該有一個output對象,來描述webpack如何build 和 package我們的代碼。在上面的例子中,我們需要打包一個UMD規(guī)范的文件到lib/目錄中。
Externals你應(yīng)該注意到我們的示例中使用了lodash。我們從外部引入依賴lodash用來更好的構(gòu)建我們的項目,而不是直接在output中include進來lodash本身。externals選項讓我們具體聲明一個外部依賴。在lodash的例子中,它的global property key(_)跟它的名字(”lodash“)是不一樣的,所以我們上面的配置告訴webpack如何在不同的規(guī)范中依賴lodash(CommonJS, AMD and browser root)。
The Babel Loader你可能注意到我們把 babel-loader 直接寫成了“babel”。這是webpack的命名規(guī)范:如果插件命名為“myLoaderName-loader”格式,那么我們在用的時候就可以直接寫做”myLoaderName“。
除了在node_modules/目錄下的.js文件,loader會作用到任何其他.js文件。compact選項中的配置表示我們不需要壓縮編譯過的文件,因為我想要我的代碼具有可讀性(一會我們會壓縮我們的代碼)。
如果我們在項目根目錄中運行webpack命令,它將根據(jù)webpack.config.js文件來build我們的代碼,并且在命令行里輸出如下的內(nèi)容:
? webpack Hash: f33a1067ef2c63b81060 Version: webpack 1.12.1 Time: 758ms Asset Size Chunks Chunk Names lib/legoQuotes.js 12.5 kB 0 [emitted] main + 7 hidden modules
現(xiàn)在如果我們查看lib/目錄,我們會發(fā)現(xiàn)一個嶄新的legoQuotes.js文件,并且它是符合webpack的UMD規(guī)范的代碼,就像下面的代碼片段:
(function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === "object" && typeof module === "object") module.exports = factory(require("lodash")); else if(typeof define === "function" && define.amd) define(["lodash"], factory); else if(typeof exports === "object") exports["legoQuotes"] = factory(require("lodash")); else root["legoQuotes"] = factory(root["_"]); })(this, function(__WEBPACK_EXTERNAL_MODULE_1__) { // MODULE CODE HERE });
UMD規(guī)范首先檢查是否是CommonJS規(guī)范,然后再檢查是否是AMD規(guī)范,然后再檢查另一種CommonJS規(guī)范,最后回落到純?yōu)g覽器引用。你可以發(fā)現(xiàn)首先在CommonJS或者AMD環(huán)境中檢查是否以“l(fā)odash”加載lodash,然后在瀏覽器中是否以_代表lodash。
What Happened, Exactly?當我們在命令行里運行webpack命令,它首先去尋找配置文件的默認名字(webpack.config.js),然后閱讀這些配置信息。它會發(fā)現(xiàn)src/index.js是主入口文件,然后開始加載這個文件和這個文件的依賴項(除了lodash,我們已經(jīng)告訴webpack這是外部依賴)。每一個依賴文件都是.js文件,所以babel loader會作用在每一個文件,把他們從ES6代碼轉(zhuǎn)化為ES5。然后所有的文件打包成為一個輸出文件,legoQuotes.js,然后把它放到lib目錄中。
觀察代碼會發(fā)現(xiàn)ES6代碼確實已經(jīng)被轉(zhuǎn)化為ES5.比如,LegoCharacter類中有一個ES5構(gòu)造函數(shù):
// around line 179 var LegoCharacter = (function () { function LegoCharacter(_ref) { var character = _ref.character; var actor = _ref.actor; _classCallCheck(this, LegoCharacter); this.actor = actor; this.name = character; this.sayings = ["I haven"t been given any funny quotes yet."]; } _createClass(LegoCharacter, [{ key: "saySomething", value: function saySomething() { return this.sayings[(0, _utils.getRandom)(0, this.sayings.length - 1)]; } }]); return LegoCharacter; })();It’s Usable!
這時我們就可以include這個打包好的文件到所有的瀏覽器(IE9+,當然~)中,也可以在node中運行完美,只要babel運行時依賴完美。
如果我們想在瀏覽器使用,它看起來會像下面的樣子:
Lego Quote Module Example
你會看到我們已經(jīng)依賴legoQuotes.js(就在babel的browser-polyfill.js下面),就像其他依賴一樣使用標簽。我們的main.js使用了legoQuotes庫,看起來是這個樣子:
// main.js ( function( legoQuotes ) { var btn = document.getElementById( "btnMore" ); var quote = document.getElementById( "quote" ); function writeQuoteToDom() { quote.innerHTML = legoQuotes.getRandomQuote(); } btn.addEventListener( "click", writeQuoteToDom ); writeQuoteToDom(); } )( legoQuotes );
在node環(huán)境中使用,是這個樣子:
require("babel/polyfill"); var lego = require("./lib/legoQuotes.js"); console.log(lego.getRandomQuote()); // > Wyldstyle: Come with me if you want to not die.Moving To Gulp
Babel和webpack的命令行工具都非常有用和高效,但是我更傾向于用類似于Gulp的自動化構(gòu)建工具來執(zhí)行其他類似的任務(wù)。如果你有很多項目,那么你會體會到構(gòu)建命令一致性所帶來的好處,我們只需要記住類似gulp someTaskName的命令,而不需要記很多其他命令。在大多數(shù)情況下,這無所謂對與錯,如果你喜歡其他的命令行工具,就去使用它。在我看來使用Gulp是一個簡單而高效的選擇。
SETTING UP A BUILD TASK首先,我們要安裝Gulp:
npm install -g gulp
接下來我們創(chuàng)建一個gulpfile配置文件。然后我們運行npm install --save-dev webpack-stream命令,來安裝和使用webpack-streamgulp 插件。這個插件可以讓webpack在gulp任務(wù)中完美運行。
// gulpfile.js var gulp = require( "gulp" ); var webpack = require( "webpack-stream" ); gulp.task( "build", function() { return gulp.src( "src/index.js" ) .pipe( webpack( require( "./webpack.config.js" ) ) ) .pipe( gulp.dest( "./lib" ) ) } );
現(xiàn)在我已經(jīng)把index.js放到了gulp的src中并且寫入了output目錄,那么我需要修改webpack.config.js文件,我刪除了entry并且更新了filename。我還添加了devtool配置,它的值為#inline-source-map(這將會在一個文件末尾寫入一個source map):
// webpack.config.js module.exports = { output: { library: "legoQuotes", libraryTarget: "umd", filename: "legoQuotes.js" }, devtool: "#inline-source-map", externals: [ { lodash: { root: "_", commonjs: "lodash", commonjs2: "lodash", amd: "lodash" } } ], module: { loaders: [ { test: /.js$/, exclude: /node_modules/, loader: "babel", query: { compact: false } } ] } };WHAT ABOUT MINIFYING?
我很高興你問了這個問題!我們用gulp-uglify,配合使用gulp-sourcemaps(給我們的min文件生成source map),gulp-rename(我們給壓縮文件重命名,這樣就不會覆蓋未壓縮的原始文件),來完成代碼壓縮工作。我們添加它們到我們的項目中:
npm install --save-dev gulp-uglify gulp-sourcemaps gulp-rename
我們的未壓縮文件依然有行內(nèi)的source map,但是gulp-sourcemaps的作用是為壓縮文件生成一個多帶帶的source map文件:
// gulpfile.js var gulp = require( "gulp" ); var webpack = require( "webpack-stream" ); var sourcemaps = require( "gulp-sourcemaps" ); var rename = require( "gulp-rename" ); var uglify = require( "gulp-uglify" ); gulp.task( "build", function() { return gulp.src( "src/index.js" ) .pipe( webpack( require( "./webpack.config.js" ) ) ) .pipe( gulp.dest( "./lib" ) ) .pipe( sourcemaps.init( { loadMaps: true } ) ) .pipe( uglify() ) .pipe( rename( "legoQuotes.min.js" ) ) .pipe( sourcemaps.write( "./" ) ) .pipe( gulp.dest( "lib/" ) ); } );
現(xiàn)在在命令行里運行gulp build,我們會看到如下輸出:
? gulp build [19:08:25] Using gulpfile ~/git/oss/next-gen-js/gulpfile.js [19:08:25] Starting "build"... [19:08:26] Version: webpack 1.12.1 Asset Size Chunks Chunk Names legoQuotes.js 23.3 kB 0 [emitted] main [19:08:26] Finished "build" after 1.28 s
現(xiàn)在在lib/目錄里有三個文件:legoQuotes.js,legoQuotes.min.js 和 legoQuotes.min.js.map。
Webpack Banner Plugin如果你需要在你打包好的文件頭部添加licence等注釋信息,webpack可以簡單實現(xiàn)。我更新了webpack.config.js文件,添加了BannerPlugin。我不喜歡親自去編輯這些注釋信息,所以我引入了package.json文件來獲取這些關(guān)于庫的信息。我還把webpack.config.js寫成了ES6的格式,可以使用新特性template string來書寫這些信息。在webpack.config.js文件底部可以看到我們添加了plugins屬性,目前BannerPlugin使我們唯一使用的插件:
// webpack.config.js import webpack from "webpack"; import pkg from "./package.json"; var banner = ` ${pkg.name} - ${pkg.description} Author: ${pkg.author} Version: v${pkg.version} Url: ${pkg.homepage} License(s): ${pkg.license} `; export default { output: { library: pkg.name, libraryTarget: "umd", filename: `${pkg.name}.js` }, devtool: "#inline-source-map", externals: [ { lodash: { root: "_", commonjs: "lodash", commonjs2: "lodash", amd: "lodash" } } ], module: { loaders: [ { test: /.js$/, exclude: /node_modules/, loader: "babel", query: { compact: false } } ] }, plugins: [ new webpack.BannerPlugin( banner ) ] };
(Note: 值得注意的是當我把webpack.config.js寫成ES6,就不能再使用webpack命令行工具來運行它了。)
我們的gulpfile.js也做了兩個更新:在第一行添加了babel register hook;我們傳入了gulp-uglify 的配置信息:
// gulpfile.js require("babel/register"); var gulp = require( "gulp" ); var webpack = require( "webpack-stream" ); var sourcemaps = require( "gulp-sourcemaps" ); var rename = require( "gulp-rename" ); var uglify = require( "gulp-uglify" ); gulp.task( "build", function() { return gulp.src( "src/index.js" ) .pipe( webpack( require( "./webpack.config.js" ) ) ) .pipe( gulp.dest( "./lib" ) ) .pipe( sourcemaps.init( { loadMaps: true } ) ) .pipe( uglify( { // This keeps the banner in the minified output preserveComments: "license", compress: { // just a personal preference of mine negate_iife: false } } ) ) .pipe( rename( "legoQuotes.min.js" ) ) .pipe( sourcemaps.write( "./" ) ) .pipe( gulp.dest( "lib/" ) ); } );What’s Next?
我們已經(jīng)為我們的旅途開了個好頭!!到目前為止我們已經(jīng)用Babel 和 webpack命令行工具構(gòu)建了我們的項目,然后我們用gulp(和相關(guān)插件)自動化構(gòu)建打包我們的項目。這篇文章的代碼包含了example/文件夾,在其中有瀏覽器端和node端的示例。在下一篇文章中,我們將用 ESLint 和 JSCS 來檢查我們的代碼,用 mocha 和 chai 來書寫測試,用 Karma 來跑這些測試,用 istanbul 來計量測試的覆蓋面。同時,你可以看另一篇非常棒的文章--Designing Better JavaScript APIs,它可以幫助你寫出更好的模塊代碼。
譯自 Writing Next Generation Reusable JavaScript Modules in ECMAScript 6
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/78657.html
摘要:前端工程化的演化。前端較為流行的單元測試,等自動化測試自動化測試是軟件通過模擬瀏覽器,對頁面進行操作,判斷是否產(chǎn)生預(yù)想的效果。 前端工程化 ??前端工程化的概念在近些年來逐漸成為主流構(gòu)建大型web應(yīng)用不可或缺的一部分,在此我通過以下這三方面總結(jié)一下自己的理解。 為什么需要前端工程化。 前端工程化的演化。 怎么實現(xiàn)前端工程化。 為什么需要工程化 ??隨著近些年來前端技術(shù)的不斷發(fā)展,越...
摘要:原文地址原文作者譯文出自掘金翻譯計劃譯者校對者統(tǒng)一樣式語言在過去幾年中,我們見證了的興起,尤其是在社區(qū)。根本上來說,純粹用于只是一個命名規(guī)范,它要求樣式的類名要遵守的模式。 原文地址:A Unified Styling Language 原文作者:Mark Dalgleish 譯文出自:掘金翻譯計劃 譯者:ZhangFe 校對者:JackGit,yifili09 統(tǒng)一樣式語言 在過...
摘要:也是一款優(yōu)秀的響應(yīng)式框架站點所使用的一套框架為微信服務(wù)量身設(shè)計的一套框架一組很小的,響應(yīng)式的組件,你可以在網(wǎng)頁的項目上到處使用一個可定制的文件,使瀏覽器呈現(xiàn)的所有元素,更一致和符合現(xiàn)代標準。 GitHub 值得收藏的前端項目 整理與收集的一些比較優(yōu)秀github項目,方便自己閱讀,順便分享出來,大家一起學(xué)習(xí),本篇文章會持續(xù)更新,版權(quán)歸原作者所有。歡迎github star與fork 預(yù)...
摘要:轉(zhuǎn)載來源包管理器管理著庫,并提供讀取和打包它們的工具。能構(gòu)建更好應(yīng)用的客戶端包管理器。一個整合和的最佳思想,使開發(fā)者能快速方便地組織和編寫前端代碼的下一代包管理器。很棒的組件集合。隱秘地使用和用戶數(shù)據(jù)。 轉(zhuǎn)載來源:https://github.com/jobbole/aw... 包管理器管理著 javascript 庫,并提供讀取和打包它們的工具。?npm – npm 是 javasc...
閱讀 3714·2021-11-11 11:00
閱讀 2190·2021-10-08 10:05
閱讀 2704·2021-10-08 10:04
閱讀 3218·2021-09-30 09:48
閱讀 3801·2021-09-27 14:10
閱讀 1710·2021-09-09 09:33
閱讀 2106·2019-08-30 15:55
閱讀 1611·2019-08-30 13:53