>  기사  >  웹 프론트엔드  >  Node.js는 Detail_node.js에서 BigPipe를 구현합니다.

Node.js는 Detail_node.js에서 BigPipe를 구현합니다.

WBOY
WBOY원래의
2016-05-16 16:29:031561검색

BigPipe는 웹페이지 로딩 속도를 최적화하기 위해 Facebook이 개발한 기술입니다. 인터넷에는 node.js를 사용하여 구현한 기사가 거의 없습니다. 실제로 node.js뿐만 아니라 다른 언어로 BigPipe를 구현한 경우도 인터넷에서 거의 없습니다. 이 기술이 등장한 지 오랜 시간이 지나서 나는 전체 웹 페이지 프레임이 먼저 전송된 후 페이지의 모듈을 요청하기 위해 또 다른 또는 여러 개의 Ajax 요청이 사용되었다고 생각했습니다. 얼마 전까지만 해도 BigPipe의 핵심 개념은 하나의 HTTP 요청만 사용하는 것이지만 페이지 요소가 순서대로 전송되지 않는다는 것을 배웠습니다.

이 핵심 개념을 이해하면 node.js의 비동기 기능 덕분에 node.js로 BigPipe를 쉽게 구현할 수 있습니다. 이 기사에서는 단계별 예제를 사용하여 BigPipe 기술의 기원과 node.js를 기반으로 한 간단한 구현을 설명합니다.

간단히 설명하기 위해 jade를 템플릿 엔진으로 선택하고, 엔진의 하위 템플릿(부분) 기능을 사용하지 않고 하위 템플릿 이후에 HTML을 사용합니다. 상위 템플릿의 데이터로 렌더링됩니다.

먼저 nodejs-bigpipe 폴더를 생성하고 다음과 같이 package.json 파일을 작성합니다.

코드 복사 코드는 다음과 같습니다.

{
"name": "bigpipe-실험"
, "버전": "0.1.0"
, "비공개": 사실
, "종속성": {
"express": "3.x.x"
, "통합": "최신"
, "jade": "최신"
}
}

npm install을 실행하여 이 세 가지 라이브러리를 설치하면 jade 호출을 용이하게 하는 데 사용됩니다.

가장 간단한 두 파일을 먼저 시도해 보겠습니다.

app.js:

코드 복사 코드는 다음과 같습니다.

var express = require('express')
, 단점 = require('통합')
, 옥 = require('jade')
, 경로 = require('경로')

var app = express()

app.engine('jade', cons.jade)
app.set('views', path.join(__dirname, 'views'))
app.set('뷰 엔진', '옥')

app.use(함수(req, res) {
res.render('레이아웃', {
s1: "안녕하세요, 저는 첫 번째 섹션입니다."
, s2: "안녕하세요. 저는 2부입니다."
})
})

app.listen(3000)

views/layout.jade

코드 복사 코드는 다음과 같습니다.

문서형 HTML

머리
제목 Hello, World!
스타일
섹션 {
        여백: 20px 자동;
테두리: 1px 점선 회색;
       너비: 80%;
높이: 150px;
}

섹션#s1!=s1
섹션#s2!=s2

효과는 다음과 같습니다.

다음으로 두 개의 섹션 템플릿을 두 개의 서로 다른 템플릿 파일에 넣습니다.

views/s1.jade:

코드 복사 코드는 다음과 같습니다.

h1 부분 1
.content!=콘텐츠

views/s2.jade:

코드 복사 코드는 다음과 같습니다.

h1 부분 2
.content!=콘텐츠

layout.jade 스타일에 일부 스타일 추가

코드 복사 코드는 다음과 같습니다.

섹션 h1 {
글꼴 크기: 1.5;
패딩: 10px 20px;
여백: 0;
border-bottom: 1px 점선 회색;
}
섹션 div {
여백: 10px;
}

app.js의 app.use() 부분을 다음으로 변경하세요.

코드 복사 코드는 다음과 같습니다.

변수 온도 = {
s1: jade.compile(fs.readFileSync(path.join(__dirname, 'views', 's1.jade')))
, s2: jade.compile(fs.readFileSync(path.join(__dirname, 'views', 's2.jade')))
}
app.use(함수(req, res) {
res.render('레이아웃', {
s1: temp.s1({ content: "안녕하세요, 저는 첫 번째 섹션입니다." })
, s2: temp.s2({ content: "안녕하세요. 저는 두 번째 섹션입니다." })
})
})

"하위 템플릿이 상위 템플릿의 데이터로 렌더링된 후 HTML을 사용합니다"라고 말하기 전에는 temp.s1과 temp.s2의 두 가지 메소드가 s1.jade와 2개의 파일을 생성한다는 의미입니다. s2.jade.HTML 코드를 만든 다음 이 두 코드 조각을layout.jade의 두 변수 s1과 s2의 값으로 사용합니다.

현재 페이지는 다음과 같습니다.

일반적으로 두 섹션의 데이터는 별도로 획득됩니다. 데이터베이스를 쿼리하든 RESTful 요청을하든 두 가지 기능을 사용하여 이러한 비동기 작업을 시뮬레이션합니다.

코드 복사 코드는 다음과 같습니다.

var getData = {
d1: 함수(fn) {
​ ​ setTimeout(fn, 3000, null, { content: "안녕하세요, 저는 첫 번째 섹션입니다." })
}
, d2: 함수(fn) {
​ ​ setTimeout(fn, 5000, null, { content: "안녕하세요. 저는 두 번째 섹션입니다." })
}
}

이런 식으로 app.use()의 로직은 더 복잡해집니다. 가장 간단한 처리 방법은 다음과 같습니다.

코드 복사 코드는 다음과 같습니다.

app.use(함수(req, res) {
getData.d1(함수 (err, s1data) {
GetData.d2(함수 (err, s2data) {
res.render('레이아웃', {
          s1: temp.s1(s1data)
, , s2: temp.s2(s2data)
})
})
})
})

이것도 원하는 결과를 얻을 수 있지만 이 경우 반환하는 데 8초가 걸립니다.

실제로 구현 로직을 보면 getData.d1의 결과가 반환된 후에만 getData.d2가 호출되며 둘 사이에는 그러한 종속성이 없음을 알 수 있습니다. 이 문제를 해결하기 위해 JavaScript 비동기 호출을 처리하는 async와 같은 라이브러리를 사용할 수 있지만 여기에 직접 직접 작성해 보겠습니다.

코드 복사 코드는 다음과 같습니다.

app.use(함수(req, res) {
var n = 2
, 결과 = {}
getData.d1(함수 (err, s1data) {
​ result.s1data = s1data
--n || 쓰기결과()
})
getData.d2(함수 (err, s2data) {
​ result.s2data = s2data
--n || 쓰기결과()
})
함수 writeResult() {
res.render('레이아웃', {
        s1: temp.s1(result.s1data)
, s2: 임시.s2(결과.s2데이터)
})
}
})

5초밖에 걸리지 않습니다.

다음 최적화 전에 jquery 라이브러리를 추가하고 CSS 스타일을 외부 파일에 넣었습니다. 그런데 나중에 사용할 브라우저 측에서 jade 템플릿을 사용하는 데 필요한 Runtime.js 파일도 추가했습니다. . app.js가 포함된 디렉터리에서 실행합니다.

코드 복사 코드는 다음과 같습니다.

mkdir 정적
CD 정적
컬 http://code.jquery.com/jquery-1.8.3.min.js -o jquery.js
ln -s ../node_modules/jade/runtime.min.js jade.js

layout.jade의 스타일 태그에 있는 코드를 꺼내 static/style.css에 넣은 다음 head 태그를 다음과 같이 변경합니다.

코드 복사 코드는 다음과 같습니다.

머리
제목 Hello, World!
링크(href="/static/style.css", rel="stylesheet")
스크립트(src="/static/jquery.js")
스크립트(src="/static/jade.js")

app.js에서는 둘 다의 다운로드 속도를 2초로 시뮬레이션하고 다음을 추가합니다.
app.use(function (req, res) {

코드 복사 코드는 다음과 같습니다.

var static = express.static(path.join(__dirname, 'static'))
app.use('/static', function (req, res, next) {
setTimeout(정적, 2000, req, res, 다음)
})

외부 정적 파일로 인해 이제 페이지가 약 7초 만에 로드됩니다.

HTTP 요청을 받자마자 헤드 부분을 반환하고 두 섹션이 비동기 작업이 완료될 때까지 기다렸다가 반환한다면 이는 HTTP 청크 전송 인코딩 메커니즘을 사용하는 것입니다. node.js에서는 res.write() 메서드를 사용하는 한 Transfer-Encoding: 청크 헤더가 자동으로 추가됩니다. 이런 방식으로 브라우저가 정적 파일을 로드하는 동안 노드 서버는 비동기 호출의 결과를 기다리고 있습니다. 먼저layout.jade에서 다음 두 줄을 삭제하겠습니다.

코드 복사 코드는 다음과 같습니다.

섹션#s1!=s1
섹션#s2!=s2

따라서 res.render()에서 객체 { s1: …, s2: … }를 제공할 필요가 없으며 res.render()는 기본적으로 res.end()를 호출하므로 수동으로 수행해야 합니다. 완료 후 렌더링 설정 콜백 함수는 res.write() 메서드를 사용합니다. 레이아웃.jade의 콘텐츠는 writeResult() 콜백 함수에 있을 필요가 없습니다. 이 요청을 받으면 반환할 수 있습니다. 콘텐츠 유형 헤더를 수동으로 추가했습니다.

코드 복사 코드는 다음과 같습니다.

app.use(함수(req, res) {
res.render('layout', function (err, str) {
(err)이면 res.req.next(err)를 반환
res.setHeader('content-type', 'text/html; charset=utf-8')
res.write(str)
})
var n = 2
getData.d1(함수 (err, s1data) {
res.write('
' temp.s1(s1data) '
')
--n || res.end()
})
getData.d2(함수 (err, s2data) {
res.write('
' temp.s2(s2data) '
')
--n || res.end()
})
})

최종 로딩 속도가 이제 약 5초로 돌아왔습니다. 실제 동작에서는 브라우저가 먼저 코드의 헤드 부분을 받은 후 3개의 정적 파일을 로드하는데 2초가 소요되며, 3초에는 Partial 1 부분이 나타나고 5초에는 Partial 2 부분이 나타납니다. 그리고 웹페이지가 로드됩니다. 스크린샷을 찍지 않겠습니다. 스크린샷 효과는 이전 5초 스크린샷과 동일합니다.

그러나 이 효과는 getData.d1이 getData.d2보다 빠르기 때문에 얻을 수 있다는 점에 유의하세요. 즉, 웹 페이지에서 어떤 블록이 먼저 반환되는지는 먼저 반환하는 인터페이스의 비동기 호출 결과에 따라 달라집니다. getData를 넣으면 반환을 위해 d1을 8초로 변경하면 Partial 2가 먼저 반환되고 s1과 s2의 순서가 바뀌어 웹 페이지의 최종 결과가 우리의 기대와 일치하지 않게 됩니다.

이 질문은 결국 BigPipe로 이어집니다. BigPipe는 웹 페이지의 각 부분의 표시 순서와 데이터 전송 순서를 분리할 수 있는 기술입니다.

기본 아이디어는 먼저 전체 웹페이지의 일반적인 프레임을 전송하는 것이며, 나중에 전송해야 할 부분은 빈 div(또는 기타 태그)로 표시됩니다.

코드 복사 코드는 다음과 같습니다.

res.render('layout', function (err, str) {
(err)가 res.req.next(err)를 반환하는 경우
res.setHeader('content-type', 'text/html; charset=utf-8')
res.write(str)
res.write('
')
})

그런 다음 JavaScript를 사용하여 반환된 데이터를 작성합니다

코드 복사 코드는 다음과 같습니다.

getData.d1(함수 (err, s1data) {
res.write('<script>$("#s1").html("' temp.s1(s1data).replace(/"/g, '\"') '")</script>')
--n || res.end()
})

s2도 비슷하게 처리됩니다. 이때, 웹페이지를 요청한 2초에는 빈 점선 박스 2개가 나타나고, 5초에는 Partial 2 부분이 나타나고, 8초에는 Partial 1 부분이 나타나고, 웹 페이지가 나타나는 것을 볼 수 있습니다. 요청이 완료되었습니다.

이쯤 되면 가장 간단한 BigPipe 기술을 구현한 웹페이지가 완성되었습니다.

작성할 웹페이지 조각에는 스크립트 태그가 있다는 점에 유의하세요. 예를 들어 s1.jade를 다음과 같이 변경하세요.

코드 복사 코드는 다음과 같습니다.

h1 부분 1
.content!=콘텐츠
스크립트
Alert("s1.jade의 경고")

그런 다음 웹페이지를 새로고침하면 알림이 실행되지 않고 웹페이지에 오류가 발생하는 것을 확인할 수 있습니다. 소스 코드를 확인하고 <script> 내부 문자열에 나타나는 </script>로 인해 오류가 발생했음을 확인하세요.

코드 복사 코드는 다음과 같습니다.
res.write('<script>$("#s1").html("' temp.s1(s1data).replace(/"/g, '\"').replace(/</script>/ g, '<
\/script>') '")')
위에서는 BigPipe의 원리와 node.js를 사용하여 BigPipe를 구현하는 기본 방법에 대해 설명했습니다. 그리고 실무에서는 어떻게 활용해야 할까요? 참고용으로 간단한 방법을 아래에 제공합니다. 코드는 다음과 같습니다.

코드 복사 코드는 다음과 같습니다.

var resProto = require('express/lib/response')
resProto.pipe = 함수(선택기, HTML, 교체) {
  this.write('<script>' '$("' 선택기 '").' <br>     (replace === true ? 'replaceWith' : 'html') <br>     '("' html.replace(/"/g, '\"').replace(/</script>/g, '<\/script>')
    '")')
}
함수 PipeName(res, 이름) {
  res.pipeCount = res.pipeCount || 0
  res.pipeMap = res.pipeMap || {}
  if (res.pipeMap[이름]) return
  res.pipeCount
  res.pipeMap[name] = this.id = ['pipe', Math.random().toString().substring(2), (new Date()).valueOf()].join('_')
  this.res = 해상도
  this.name = 이름
}
resProto.pipeName = 함수(이름) {
  새 파이프 이름(이것, 이름) 반환
}
resProto.pipeLayout = 함수(보기, 옵션) {
  var res = 이것
  Object.keys(options).forEach(함수 (키) {
    if (options[key] instanceof PipeName) 옵션[key] = ''
  })
  res.render(view, options, function (err, str) {
    (err)가 res.req.next(err)를 반환하는 경우
    res.setHeader('content-type', 'text/html; charset=utf-8')
    res.write(str)
    if (!res.pipeCount) res.end()
  })
}
resProto.pipePartial = 함수(이름, 보기, 옵션) {
  var res = 이것
  res.render(view, options, function (err, str) {
    (err)가 res.req.next(err)를 반환하는 경우
    res.pipe('#' res.pipeMap[이름], str, true)
    --res.pipeCount || res.end()
  })
}
app.get('/', 함수(req, res) {
  res.pipeLayout('레이아웃', {
      s1: res.pipeName('s1name')
    , s2: res.pipeName('s2name')
  })
  getData.d1(함수 (err, s1data) {
    res.pipePartial('s1name', 's1', s1data)
  })
  getData.d2(함수 (err, s2data) {
    res.pipePartial('s2name', 's2', s2data)
  })
})

还要에서 레이아웃.jade 把两个 섹션 添加回来:

复代码 代码如下:

섹션#s1!=s1
섹션#s2!=s2

这里的思路是,需要pipe 的内容先用一个span 标签位,异步获取数据并渲染完成应的 HTML 代码后再输流给浏览器,jQuery 대체 방법으로 대체 스팬 元素替换掉。

本文的代码재 https://github.com/undozen/bigpipe-on-node ,我把每一步做成一个 커밋 了,希望你 clone 到本地实际运行并 hack 一下看看.因为后면几步涉及到加载顺序了,确实要自己打开浏览器才能体验到而无法从截图上看到 (其实应该可以用 gif 动画实现,但是我懒得做了)。

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.