欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

使用Node.js實(shí)現(xiàn)HTTP 206內(nèi)容分片的教程

 更新時(shí)間:2015年06月23日 11:08:17   投稿:goldensun  
這篇文章主要介紹了使用Node.js實(shí)現(xiàn)HTTP 206內(nèi)容分片的教程,Node.js是一款用于服務(wù)器端的JavaScript框架,需要的朋友可以參考下

 介紹

在本文中,我會(huì)闡述HTTP狀態(tài)206 分部分內(nèi)容 的基礎(chǔ)概念,并使用Node.js一步步地實(shí)現(xiàn)它. 我們還將用一個(gè)基于它用法最常見(jiàn)場(chǎng)景的示例來(lái)測(cè)試代碼:一個(gè)能夠在任何時(shí)間點(diǎn)開(kāi)始播放視頻文件的HTML5頁(yè)面.
Partial Content 的簡(jiǎn)要介紹

HTTP 的 206 Partial Content 狀態(tài)碼和其相關(guān)的消息頭提供了讓瀏覽器以及其他用戶代理從服務(wù)器接收部分內(nèi)容而不是全部?jī)?nèi)容,這樣一種機(jī)制. 這一機(jī)制被廣泛使用在一個(gè)被大多數(shù)瀏覽器和諸如Windows Media Player和VLC Player這樣的播放器所支持視頻文件的傳輸上.
 

基礎(chǔ)的流程可以用下面這幾步描述:

  •     瀏覽器請(qǐng)求內(nèi)容.
  •     服務(wù)器告訴瀏覽器,該內(nèi)容可以使用 Accept-Ranges 消息頭進(jìn)行分部分請(qǐng)求.
  •     瀏覽器重新發(fā)送請(qǐng)求,用 Range 消息頭告訴服務(wù)器需要的內(nèi)容范圍.

    服務(wù)器會(huì)分如下兩種情況響應(yīng)瀏覽器的請(qǐng)求:

  •         如果范圍是合理的,服務(wù)器會(huì)返回所請(qǐng)求的部分內(nèi)容,并帶上 206 Partial Content 狀態(tài)碼. 當(dāng)前內(nèi)容的范圍會(huì)在 Content-Range 消息頭中申明.
  •         如果范圍是不可用的(例如,比內(nèi)容的總字節(jié)數(shù)大), 服務(wù)器會(huì)返回 416 請(qǐng)求范圍不合理 Requested Range Not Satisfiable 狀態(tài)碼. 可用的范圍也會(huì)在 Content-Range 消息頭中聲明.

讓我們來(lái)看看這幾個(gè)步驟中的每一個(gè)關(guān)鍵消息頭.

Accept-Ranges: 字節(jié)(bytes)

這是會(huì)有服務(wù)器發(fā)送的字節(jié)頭,展示可以被分部分發(fā)送給瀏覽器的內(nèi)容. 這個(gè)值聲明了可被接受的每一個(gè)范圍請(qǐng)求, 大多數(shù)情況下是字節(jié)數(shù) bytes.


Range: 字節(jié)數(shù)(bytes)=(開(kāi)始)-(結(jié)束)

這是瀏覽器告知服務(wù)器所需分部分內(nèi)容范圍的消息頭. 注意開(kāi)始和結(jié)束位置是都包括在內(nèi)的,而且是從0開(kāi)始的. 這個(gè)消息頭也可以不發(fā)送兩個(gè)位置,其含義如下:

  •     如果結(jié)束位置被去掉了,服務(wù)器會(huì)返回從聲明的開(kāi)始位置到整個(gè)內(nèi)容的結(jié)束位置內(nèi)容的最后一個(gè)可用字節(jié).
  •     如果開(kāi)始位置被去掉了,結(jié)束位置參數(shù)可以被描述成從最后一個(gè)可用的字節(jié)算起可以被服務(wù)器返回的字節(jié)數(shù).

Content-Range:字節(jié)數(shù)(bytes)=(開(kāi)始)-(結(jié)束)/(總數(shù))

這個(gè)消息頭將會(huì)跟隨 HTTP 狀態(tài)碼 206 一起出現(xiàn). 開(kāi)始和結(jié)束的值展示了當(dāng)前內(nèi)容的范圍. 跟 Range 消息頭一樣, 兩個(gè)值都是包含在內(nèi)的,并且也是從零開(kāi)始的. 總數(shù)這個(gè)值聲明了可用字節(jié)的總數(shù).
 
Content-Range: */(總數(shù))

這個(gè)頭信息和上面一個(gè)是一樣的,不過(guò)是用另一種格式,并且僅在返回HTTP狀態(tài)碼416時(shí)被發(fā)送。其中總數(shù)代表了正文總共可用的字節(jié)數(shù)。

這里有一對(duì)有2048個(gè)字節(jié)文件的例子。注意省略起點(diǎn)和重點(diǎn)的區(qū)別。

請(qǐng)求開(kāi)始的1024個(gè)字節(jié)

瀏覽器發(fā)送:
 

GET /dota2/techies.mp4 HTTP/1.1
Host: localhost:8000
Range: bytes=0-1023

服務(wù)器返回:
 

HTTP/1.1 206 Partial Content
Date: Mon, 15 Sep 2014 22:19:34 GMT
Content-Type: video/mp4
Content-Range: bytes 0-1023/2048
Content-Length: 1024
 
(Content...)

沒(méi)有終點(diǎn)位置的請(qǐng)求

瀏覽器發(fā)送:
 

GET /dota2/techies.mp4 HTTP/1.1
Host: localhost:8000
Range: bytes=1024-

服務(wù)器返回:
 

HTTP/1.1 206 Partial Content
Date: Mon, 15 Sep 2014 22:19:34 GMT
Content-Type: video/mp4
Content-Range: bytes 1024-2047/2048
Content-Length: 1024
 
(Content...)


注意:服務(wù)器并不需要在單個(gè)響應(yīng)中返回所有剩下的字節(jié),特別是當(dāng)正文太長(zhǎng)或者有其他性能的考慮。所以下面的兩個(gè)例子在這種情況下也是可接受的:
 

Content-Range: bytes 1024-1535/2048
Content-Length: 512

服務(wù)器僅返回剩余正文的一半。下一次請(qǐng)求的范圍將從第1536個(gè)字節(jié)開(kāi)始。

 

Content-Range: bytes 1024-1279/2048
Content-Length: 256

服務(wù)器僅返回剩余正文的256個(gè)字節(jié)。下一次請(qǐng)求的范圍將從第1280個(gè)字節(jié)開(kāi)始。


請(qǐng)求最后512個(gè)字節(jié)

瀏覽器發(fā)送:
 

GET /dota2/techies.mp4 HTTP/1.1
Host: localhost:8000
Range: bytes=-512

服務(wù)器返回:
 

HTTP/1.1 206 Partial Content
Date: Mon, 15 Sep 2014 22:19:34 GMT
Content-Type: video/mp4
Content-Range: bytes 1536-2047/2048
Content-Length: 512
 
(Content...)

請(qǐng)求不可用的范圍:

瀏覽器發(fā)送:
 

GET /dota2/techies.mp4 HTTP/1.1
Host: localhost:8000
Range: bytes=1024-4096

服務(wù)器返回:
 

HTTP/1.1 416 Requested Range Not Satisfiable
Date: Mon, 15 Sep 2014 22:19:34 GMT
Content-Range: bytes */2048

理解了工作流和頭部信息后,現(xiàn)在我們可以用Node.js去實(shí)現(xiàn)這個(gè)機(jī)制。

開(kāi)始用Node.js實(shí)現(xiàn)

第一步:創(chuàng)建一個(gè)簡(jiǎn)單的HTTP服務(wù)器

我們將像下面的例子那樣,從一個(gè)基本的HTTP服務(wù)器開(kāi)始。這已經(jīng)可以基本足夠處理大多數(shù)的瀏覽器請(qǐng)求了。首先,我們初始化我們需要用到的對(duì)象,并且用initFolder來(lái)代表文件的位置。為了生成Content-Type頭部,我們列出文件擴(kuò)展名和它們相對(duì)應(yīng)的MIME名稱來(lái)構(gòu)成一個(gè)字典。在回調(diào)函數(shù)httpListener()中,我們將僅允許GET可用。如果出現(xiàn)其他方法,服務(wù)器將返回405 Method Not Allowed,在文件不存在于initFolder,服務(wù)器將返回404 Not Found。
 

// 初始化需要的對(duì)象
var http = require("http");
var fs = require("fs");
var path = require("path");
var url = require("url");
 
// 初始的目錄,隨時(shí)可以改成你希望的目錄
var initFolder = "C:\\Users\\User\\Videos";
 
// 將我們需要的文件擴(kuò)展名和MIME名稱列出一個(gè)字典
var mimeNames = {
  ".css": "text/css",
  ".html": "text/html",
  ".js": "application/javascript",
  ".mp3": "audio/mpeg",
  ".mp4": "video/mp4",
  ".ogg": "application/ogg", 
  ".ogv": "video/ogg", 
  ".oga": "audio/ogg",
  ".txt": "text/plain",
  ".wav": "audio/x-wav",
  ".webm": "video/webm";
};
 
http.createServer(httpListener).listen(8000);
 
function httpListener (request, response) {
  // 我們將只接受GET請(qǐng)求,否則返回405 'Method Not Allowed'
  if (request.method != "GET") { 
    sendResponse(response, 405, {"Allow" : "GET"}, null);
    return null;
  }
 
  var filename = 
    initFolder + url.parse(request.url, true, true).pathname.split('/').join(path.sep);
 
  var responseHeaders = {};
  var stat = fs.statSync(filename);
  // 檢查文件是否存在,不存在就返回404 Not Found
  if (!fs.existsSync(filename)) {
    sendResponse(response, 404, null, null);
    return null;
  }
  responseHeaders["Content-Type"] = getMimeNameFromExt(path.extname(filename));
  responseHeaders["Content-Length"] = stat.size; // 文件大小
     
  sendResponse(response, 200, responseHeaders, fs.createReadStream(filename));
}
 
function sendResponse(response, responseStatus, responseHeaders, readable) {
  response.writeHead(responseStatus, responseHeaders);
 
  if (readable == null)
    response.end();
  else
    readable.on("open", function () {
      readable.pipe(response);
    });
 
  return null;
}
 
function getMimeNameFromExt(ext) {
  var result = mimeNames[ext.toLowerCase()];
   
  // 最好給一個(gè)默認(rèn)值
  if (result == null)
    result = "application/octet-stream";
   
  return result;
}

步驟 2 - 使用正則表達(dá)式捕獲Range消息頭

有了這個(gè)HTTP服務(wù)器做基礎(chǔ),我們現(xiàn)在就可以用如下代碼處理Range消息頭了. 我們使用正則表達(dá)式將消息頭分割,以獲取開(kāi)始和結(jié)束字符串。然后使用 parseInt() 方法將它們轉(zhuǎn)換成整形數(shù). 如果返回值是 NaN (非數(shù)字not a number), 那么這個(gè)字符串就是沒(méi)有在這個(gè)消息頭中的. 參數(shù)totalLength展示了當(dāng)前文件的總字節(jié)數(shù). 我們將使用它計(jì)算開(kāi)始和結(jié)束位置.

 

function readRangeHeader(range, totalLength) {
    /*
     * Example of the method 'split' with regular expression.
     * 
     * Input: bytes=100-200
     * Output: [null, 100, 200, null]
     * 
     * Input: bytes=-200
     * Output: [null, null, 200, null]
     */
 
  if (range == null || range.length == 0)
    return null;
 
  var array = range.split(/bytes=([0-9]*)-([0-9]*)/);
  var start = parseInt(array[1]);
  var end = parseInt(array[2]);
  var result = {
    Start: isNaN(start) ? 0 : start,
    End: isNaN(end) ? (totalLength - 1) : end
  };
   
  if (!isNaN(start) && isNaN(end)) {
    result.Start = start;
    result.End = totalLength - 1;
  }
 
  if (isNaN(start) && !isNaN(end)) {
    result.Start = totalLength - end;
    result.End = totalLength - 1;
  }
 
  return result;
}

步驟 3 - 檢查數(shù)據(jù)范圍是否合理

回到函數(shù) httpListener(), 在HTTP方法通過(guò)之后,現(xiàn)在我們來(lái)檢查請(qǐng)求的數(shù)據(jù)范圍是否可用. 如果瀏覽器沒(méi)有發(fā)送 Range 消息頭過(guò)來(lái), 請(qǐng)求就會(huì)直接被當(dāng)做一般的請(qǐng)求對(duì)待. 服務(wù)器會(huì)返回整個(gè)文件,HTTP狀態(tài)將會(huì)是 200 OK. 另外我們還會(huì)看看開(kāi)始和結(jié)束位置是否比文件長(zhǎng)度更大或者相等. 只要有一個(gè)是這種情況,請(qǐng)求的數(shù)據(jù)范圍就是不能被滿足的. 返回的狀態(tài)就將會(huì)是 416 Requested Range Not Satisfiable 而 Content-Range 也會(huì)被發(fā)送. 
 

var responseHeaders = {};
  var stat = fs.statSync(filename);
  var rangeRequest = readRangeHeader(request.headers['range'], stat.size);
  
  // If 'Range' header exists, we will parse it with Regular Expression.
  if (rangeRequest == null) {
    responseHeaders['Content-Type'] = getMimeNameFromExt(path.extname(filename));
    responseHeaders['Content-Length'] = stat.size; // File size.
    responseHeaders['Accept-Ranges'] = 'bytes';
     
    // If not, will return file directly.
    sendResponse(response, 200, responseHeaders, fs.createReadStream(filename));
    return null;
  }
 
  var start = rangeRequest.Start;
  var end = rangeRequest.End;
 
  // If the range can't be fulfilled. 
  if (start >= stat.size || end >= stat.size) {
    // Indicate the acceptable range.
    responseHeaders['Content-Range'] = 'bytes */' + stat.size; // File size.
 
    // Return the 416 'Requested Range Not Satisfiable'.
    sendResponse(response, 416, responseHeaders, null);
    return null;
  }


步驟 4 - 滿足請(qǐng)求

最后使人迷惑的一塊來(lái)了。對(duì)于狀態(tài) 216 Partial Content, 我們有另外一種格式的 Content-Range 消息頭,包括開(kāi)始,結(jié)束位置以及當(dāng)前文件的總字節(jié)數(shù). 我們也還有 Content-Length 消息頭,其值就等于開(kāi)始和結(jié)束位置之間的差。在最后一句代碼中,我們調(diào)用了 createReadStream() 并將開(kāi)始和結(jié)束位置的值給了第二個(gè)參數(shù)選項(xiàng)的對(duì)象, 這意味著返回的流將只包含從開(kāi)始到結(jié)束位置的只讀數(shù)據(jù).
 

// Indicate the current range. 
  responseHeaders['Content-Range'] = 'bytes ' + start + '-' + end + '/' + stat.size;
  responseHeaders['Content-Length'] = start == end ? 0 : (end - start + 1);
  responseHeaders['Content-Type'] = getMimeNameFromExt(path.extname(filename));
  responseHeaders['Accept-Ranges'] = 'bytes';
  responseHeaders['Cache-Control'] = 'no-cache';
 
  // Return the 206 'Partial Content'.
  sendResponse(response, 206, 
    responseHeaders, fs.createReadStream(filename, { start: start, end: end }));

下面是完整的 httpListener() 回調(diào)函數(shù).

 

function httpListener(request, response) {
  // We will only accept 'GET' method. Otherwise will return 405 'Method Not Allowed'.
  if (request.method != 'GET') {
    sendResponse(response, 405, { 'Allow': 'GET' }, null);
    return null;
  }
 
  var filename =
    initFolder + url.parse(request.url, true, true).pathname.split('/').join(path.sep);
 
  // Check if file exists. If not, will return the 404 'Not Found'. 
  if (!fs.existsSync(filename)) {
    sendResponse(response, 404, null, null);
    return null;
  }
 
  var responseHeaders = {};
  var stat = fs.statSync(filename);
  var rangeRequest = readRangeHeader(request.headers['range'], stat.size);
 
  // If 'Range' header exists, we will parse it with Regular Expression.
  if (rangeRequest == null) {
    responseHeaders['Content-Type'] = getMimeNameFromExt(path.extname(filename));
    responseHeaders['Content-Length'] = stat.size; // File size.
    responseHeaders['Accept-Ranges'] = 'bytes';
 
    // If not, will return file directly.
    sendResponse(response, 200, responseHeaders, fs.createReadStream(filename));
    return null;
  }
 
  var start = rangeRequest.Start;
  var end = rangeRequest.End;
 
  // If the range can't be fulfilled. 
  if (start >= stat.size || end >= stat.size) {
    // Indicate the acceptable range.
    responseHeaders['Content-Range'] = 'bytes */' + stat.size; // File size.
 
    // Return the 416 'Requested Range Not Satisfiable'.
    sendResponse(response, 416, responseHeaders, null);
    return null;
  }
 
  // Indicate the current range. 
  responseHeaders['Content-Range'] = 'bytes ' + start + '-' + end + '/' + stat.size;
  responseHeaders['Content-Length'] = start == end ? 0 : (end - start + 1);
  responseHeaders['Content-Type'] = getMimeNameFromExt(path.extname(filename));
  responseHeaders['Accept-Ranges'] = 'bytes';
  responseHeaders['Cache-Control'] = 'no-cache';
 
  // Return the 206 'Partial Content'.
  sendResponse(response, 206, 
    responseHeaders, fs.createReadStream(filename, { start: start, end: end }));
}


測(cè)試實(shí)現(xiàn)

我們?cè)趺磥?lái)測(cè)試我們的代碼呢?就像在介紹中提到的,部分正文最常用的場(chǎng)景是流和播放視頻。所以我們創(chuàng)建了一個(gè)ID為mainPlayer并包含一個(gè)<source/>標(biāo)簽的<video/>。函數(shù)onLoad()將在mainPlayer預(yù)讀取當(dāng)前視頻的元數(shù)據(jù)時(shí)被觸發(fā),這用于檢查在URL中是否有數(shù)字參數(shù),如果有,mainPlayer將跳到指定的時(shí)間點(diǎn)。

 

<!DOCTYPE html>
<html>
  <head>
    <script type="text/javascript">
 
      function onLoad() {
        var sec = parseInt(document.location.search.substr(1));
         
        if (!isNaN(sec))
          mainPlayer.currentTime = sec;
      }
     
    </script>
    <title>Partial Content Demonstration</title>
  </head>
  <body>
    <h3>Partial Content Demonstration</h3>
    <hr />
    <video id="mainPlayer" width="640" height="360" 
      autoplay="autoplay" controls="controls" onloadedmetadata="onLoad()">
      <source src="dota2/techies.mp4" />
    </video>
  </body>
</html>

 

現(xiàn)在我們把頁(yè)面保存為"player.html"并和"dota2/techies.mp4"一起放在initFolder目錄下。然后在瀏覽器中打開(kāi)URL:http://localhost:8000/player.html

在Chrome中看起來(lái)像這樣:

2015623105803917.png (680×535)

因?yàn)樵赨RL中沒(méi)有任何參數(shù),文件將從最開(kāi)始出播放。

接下來(lái)就是有趣的部分了。讓我們?cè)囍蜷_(kāi)這個(gè)然后看看發(fā)生了什么:http://localhost:8000/player.html?60

2015623105918021.png (680×535)

如果你按F12來(lái)打開(kāi)Chrome的開(kāi)發(fā)者工具,切換到網(wǎng)絡(luò)標(biāo)簽頁(yè),然后點(diǎn)擊查看最近一次日志的詳細(xì)信息。你會(huì)發(fā)現(xiàn)范圍的頭信息(Range)被你的瀏覽器發(fā)送了:
 

Range:bytes=225084502-

很有趣,對(duì)吧?當(dāng)函數(shù)onLoad()改變currentTime屬性的時(shí)候,瀏覽器計(jì)算這部視頻60秒處的字節(jié)位置。因?yàn)閙ainPlayer已經(jīng)預(yù)加載了元數(shù)據(jù),包括格式、比特率和其他基本信息,這個(gè)起始位置立刻就被得到了。之后,瀏覽器就可以下載并播放視頻而不需要請(qǐng)求開(kāi)頭的60秒了。成功了!
 

結(jié)論

我們已經(jīng)用Node.js來(lái)實(shí)現(xiàn)支持部分正文的HTTP服務(wù)器端了。我們也用HTML5頁(yè)面測(cè)試了。但這只是一個(gè)開(kāi)始。如果你對(duì)頭部信息和工作流這些都已經(jīng)理解透徹了,你可以試著用其他像ASP.NET MVC或者WCF服務(wù)這類框架來(lái)實(shí)現(xiàn)它。但是不要忘記啟動(dòng)任務(wù)管理器來(lái)查看CPU和內(nèi)存的使用。像我們?cè)谥坝懻摰降?,服?wù)器沒(méi)有在單個(gè)響應(yīng)中返回所用剩余的字節(jié)。要找到性能的平衡點(diǎn)將是一項(xiàng)重要的任務(wù)。

相關(guān)文章

  • Node.js下自定義錯(cuò)誤類型詳解

    Node.js下自定義錯(cuò)誤類型詳解

    在JavaScript里面,運(yùn)行過(guò)程中的錯(cuò)誤的類型總是被人忽略,這篇文章給大家詳細(xì)介紹了如何在Node.js下自定義錯(cuò)誤類型,對(duì)大家學(xué)習(xí)或者使用Node.js具有一定的參考借鑒價(jià)值,有需要的朋友們可以參考借鑒,下面來(lái)一起看看吧。
    2016-10-10
  • node.js中的querystring.parse方法使用說(shuō)明

    node.js中的querystring.parse方法使用說(shuō)明

    這篇文章主要介紹了node.js中的querystring.parse方法使用說(shuō)明,本文介紹了querystring.parse的方法說(shuō)明、語(yǔ)法、接收參數(shù)、使用實(shí)例和實(shí)現(xiàn)源碼,需要的朋友可以參考下
    2014-12-12
  • nodeJs爬蟲(chóng)的技術(shù)點(diǎn)總結(jié)

    nodeJs爬蟲(chóng)的技術(shù)點(diǎn)總結(jié)

    本篇文章給大家總結(jié)了關(guān)于nodeJs爬蟲(chóng)的技術(shù)點(diǎn)的相關(guān)知識(shí),對(duì)爬蟲(chóng)有興趣的朋友可以跟著學(xué)習(xí)參考下。
    2018-05-05
  • Nodejs極簡(jiǎn)入門(mén)教程(一):模塊機(jī)制

    Nodejs極簡(jiǎn)入門(mén)教程(一):模塊機(jī)制

    這篇文章主要介紹了Nodejs極簡(jiǎn)入門(mén)教程(一):模塊機(jī)制,本文講解了模塊基礎(chǔ)知識(shí)、模塊的加載、包等內(nèi)容,需要的朋友可以參考下
    2014-10-10
  • node運(yùn)行js獲得輸出的三種方式示例詳解

    node運(yùn)行js獲得輸出的三種方式示例詳解

    這篇文章主要介紹了node運(yùn)行js獲得輸出的三種方式,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-07-07
  • 詳解Node.js使用token進(jìn)行認(rèn)證的簡(jiǎn)單示例

    詳解Node.js使用token進(jìn)行認(rèn)證的簡(jiǎn)單示例

    這篇文章主要介紹了詳解Node.js使用token進(jìn)行認(rèn)證的簡(jiǎn)單示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-05-05
  • Node.js使用MongoDB的ObjectId作為查詢條件的方法

    Node.js使用MongoDB的ObjectId作為查詢條件的方法

    這篇文章主要介紹了Node.js使用MongoDB的ObjectId作為查詢條件的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-09-09
  • 詳解npm和cnpm混用的坑

    詳解npm和cnpm混用的坑

    有沒(méi)有遇到過(guò)npm和cnpm一起用的時(shí)候出現(xiàn)奇奇怪怪的問(wèn)題呢? 有沒(méi)有遇到過(guò)cnpm在支付寶小程序上面安裝包無(wú)效?本文就詳解一下npm和cnpm混用的坑,感興趣的可以了解下
    2021-07-07
  • 安裝node-sass的方法步驟

    安裝node-sass的方法步驟

    本文主要介紹了安裝node-sass的方法步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-05-05
  • npm 下載指定版本的組件方法

    npm 下載指定版本的組件方法

    今天小編就為大家分享一篇npm 下載指定版本的組件方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2018-05-05

最新評(píng)論