AMP

Node.js AMP Optimizer ๊ฐ€์ด๋“œ

์ด ๊ฐ€์ด๋“œ์—์„œ๋Š” AMP Optimizer์˜ Node.js ๋ฒ„์ „์„ ์„ค์ •ํ•˜๊ณ  ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.

์„ค์ •

๋‹ค์Œ์„ ์‚ฌ์šฉํ•˜์—ฌ NPM์„ ํ†ตํ•ด ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค.

npm install @ampproject/toolbox-optimizer

์‚ฌ์šฉ

AMP Optimizer API๋Š” HTML ๋ฌธ์ž์—ด์„ ์ž…๋ ฅํ•˜์—ฌ ํ•ด๋‹น HTML ๋ฌธ์ž์—ด์˜ ์ตœ์ ํ™”๋œ ๋ฒ„์ „์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ ์‚ฌ์šฉ ์‚ฌ๋ก€๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

const AmpOptimizer = require('@ampproject/toolbox-optimizer');

// create the AMP Optimizer instance
const ampOptimizer = AmpOptimizer.create();

const html = '<h1>Hello World!</h1>';

const optimizedHtml = await ampOptimizer.transformHtml(html);

๋นŒ๋“œ ํƒ€์ž„์œผ๋กœ ์ตœ์ ํ™”๋œ AMP ์ƒ์„ฑ

์ •์  ์‚ฌ์ดํŠธ์˜ ๊ฒฝ์šฐ ์‚ฌ์ดํŠธ ๋นŒ๋“œ ์‹œ ๋นŒ๋“œ ํƒ€์ž„์œผ๋กœ AMP ํŽ˜์ด์ง€๋ฅผ ์ตœ์ ํ™”ํ•˜๋Š” ๊ฒƒ์ด ์ตœ์„ ์ž…๋‹ˆ๋‹ค. ๋นŒ๋“œ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•ด๋‹น ํŽ˜์ด์ง€๋ฅผ Gulp.js์— ํ†ตํ•ฉํ•˜๋Š” ์˜ˆ์‹œ๋ฅผ ํ™•์ธํ•ด ๋ณด์„ธ์š”. ์ด ์˜ˆ์‹œ์—๋Š” ์‚ฌ์šฉ์ž ์ง€์ • ๋ณ€ํ™˜์ด ์ถ”๊ฐ€๋˜์–ด src ํด๋”์˜ ๋ชจ๋“  HTML ํŒŒ์ผ์„ ์ตœ์ ํ™”ํ•ฉ๋‹ˆ๋‹ค.

const {src, dest} = require('gulp');
const through2 = require('through2');

const AmpOptimizer = require('@ampproject/toolbox-optimizer');
const ampOptimizer = AmpOptimizer.create();

function build(cb) {
  return src('src/*.html')
    .pipe(
      through2.obj(async (file, _, cb) => {
        if (file.isBuffer()) {
          const optimizedHtml = await ampOptimizer.transformHtml(
            file.contents.toString()
          );
          file.contents = Buffer.from(optimizedHtml);
        }
        cb(null, file);
      })
    )
    .pipe(dest('dist/'));
}

exports.default = build;

๋ Œ๋” ํƒ€์ž„

๋™์  ํŽ˜์ด์ง€์˜ ๊ฒฝ์šฐ ์„œ๋ฒ„์—์„œ ํŽ˜์ด์ง€๋ฅผ ๋ Œ๋”๋งํ•ด์•ผ ํ•  ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฐ ๊ฒฝ์šฐ์— ํŽ˜์ด์ง€ ๋ Œ๋”๋ง ํ›„ AMP Optimizer๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Express.js ์„œ๋ฒ„๋กœ์˜ ์ƒ˜ํ”Œ ํ†ตํ•ฉ์„ ํ™•์ธํ•˜์„ธ์š”. AMP ์ตœ์ ํ™”๋ฅผ Express ๋ผ์šฐํ„ฐ๋กœ ํ†ตํ•ฉํ•˜๋Š” ํ•œ ๊ฐ€์ง€ ๋ฐฉ์‹์€ ํ…œํ”Œ๋ฆฟ์ด ๋ Œ๋”๋ง๋œ ํ›„ ์ฝœ๋ฐฑํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

const express = require('express');
const router = express.Router();
const AmpOptimizer = require('@ampproject/toolbox-optimizer');
const ampOptimizer = AmpOptimizer.create();

router.get('/', (req, res) => {
  const locals = {title: 'Express with AMP Optimizer'};
  res.render('index', locals, async (err, html) => {
    const optimizedHtml = await ampOptimizer.transformHtml(html);
    res.send(optimizedHtml);
  });
});

module.exports = router;

๋ Œ๋”๋ง ์ง€์—ฐ์„ ๋ฐฉ์ง€ํ•˜๋ ค๋ฉด ์„œ๋ฒ„์—์„œ AMP Optimizer ์‚ฌ์šฉ ์‹œ ์บ์‹ฑ ๋˜๋Š” CDN์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.

๊ตฌ์„ฑ

AMP Optimizer๋Š” ๋Œ€๋‹ค์ˆ˜์˜ ๊ฒฝ์šฐ ์ ์ ˆํžˆ ์ž‘๋™ํ•˜๋Š” ํ•ฉ๋ฆฌ์ ์ธ ๊ธฐ๋ณธ ๊ตฌ์„ฑ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ํŠน์ • ์‚ฌ์šฉ ์‚ฌ๋ก€์— ๋”ฐ๋ผ ๋ณ€ํ™˜์„ ์‚ฌ์šฉ์ž ์ง€์ •ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ€๋Šฅํ•œ ๋ชจ๋“  ์˜ต์…˜์˜ ์ „์ฒด ๋ชจ๊ณก์€ ์—ฌ๊ธฐ์—์„œ ํ™•์ธํ•˜์„ธ์š”.

๋ช‡ ๊ฐ€์ง€ ์ค‘์š” ์˜ต์…˜์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • lts: true๋Š” AMP ๋Ÿฐํƒ€์ž„ ๋ฐ ์ปดํฌ๋„ŒํŠธ์˜ ์•ˆ์ •์ ์ธ ์žฅ๊ธฐ URL ํ™œ์„ฑํ™” ์‹œ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
  • verbose: true๋Š” ์„ธ๋ถ€์ ์ธ ๋””๋ฒ„๊ทธ ์ถœ๋ ฅ ๊ฐ’์— ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ํŠนํžˆ AMP ์ƒ์šฉ๊ตฌ๋ฅผ ์‚ญ์ œํ•  ์ˆ˜ ์—†๋Š” ์ด์œ ๋ฅผ ์‹๋ณ„ํ•˜๋Š” ๋ฐ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • imageOptimizer: ์ฃผ์–ด์ง„ ์ด๋ฏธ์ง€ src์˜ srcset URL์„ ๊ณ„์‚ฐํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ์ œ๊ณตํ•˜์—ฌ ์ž๋™ ์ด๋ฏธ์ง€ srcset ์ƒ์„ฑ์„ ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค. ํ•จ์ˆ˜๋Š” ์ฃผ์–ด์ง„ ๋„ˆ๋น„์˜ src ์ด๋ฏธ์ง€ ๋ฒ„์ „์„ ๊ฐ€๋ฆฌํ‚ค๋Š” URL์„ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฏธ์ง€๊ฐ€ ์ง€์›๋˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ ๊ฑฐ์ง“ ๊ฐ™์€(Falsy) ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ ์„น์…˜์—์„œ ์ž์„ธํ•œ ๋‚ด์šฉ์„ ํ™•์ธํ•˜์„ธ์š”.

์ด๋ฏธ์ง€ ์ตœ์ ํ™”

AMP Optimizer๋Š” layout ์ •์˜๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ฃผ์–ด์ง„ amp-img์˜ srcset ๊ฐ’์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ ์ ˆํžˆ ์ž‘๋™ํ•˜๋ ค๋ฉด ์ด๋ฏธ์ง€์˜ src๋ฅผ ๋งคํ•‘ํ•˜๋Š” ํ•จ์ˆ˜ ๋ฐ ํฌ๊ธฐ๊ฐ€ ๋ณ€๊ฒฝ๋œ srcset ์†Œ์Šค ๊ฐ’์˜ width๋ฅผ ์ œ๊ณตํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฏธ์ง€ ํฌ๊ธฐ ๋ณ€๊ฒฝ์€ AMP Optimizer๋ฅผ ํ†ตํ•ด ์ˆ˜ํ–‰๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๋นŒ๋“œ ํƒ€์ž„์œผ๋กœ(์˜ˆ: ์ •์  ์‚ฌ์ดํŠธ) ๋˜๋Š” thumbor ๋“ฑ์˜ ์ด๋ฏธ์ง€ ํ˜ธ์ŠคํŒ… ์„œ๋น„์Šค๋กœ ์ˆ˜ํ–‰๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ์€ src์— ์ด๋ฏธ์ง€ ๋„ˆ๋น„๋ฅผ ์ถ”๊ฐ€ํ•œ ๊ตฌํ˜„ ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค.

const ampOptimizer = AmpOptimizer.create({
  // parameters are the amp-img `src` and the `width` of the to be generated srcset source value
  imageOptimizer: (src, width) => {
    // we cannot rename if the image does not have a file extension
    const index = src.lastIndexOf('.');
    if (index === -1) {
      // return null means we won't generate a srcset source value for this width
      return null;
    }
    const prefix = src.substring(0, index);
    const postfix = src.substring(index, src.length);
    return `${prefix}.${width}w${postfix}`;
  };
})

์ด ๊ตฌํ˜„์„ ์‚ฌ์šฉํ•˜์—ฌ AMP Optimizer๋Š” ๋‹ค์Œ amp-img ์„ ์–ธ์„ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

<!-- Injects srcset for responsive layout -->
<amp-img
  src="image1.png"
  width="400"
  height="800"
  layout="responsive"
></amp-img>
<!-- Ignores existing srcset -->
<amp-img
  layout="fill"
  srcset="image-1x.png 1x,
                             image-2x.png 2x"
></amp-img>

๋ณ€ํ™˜๋œ ์ฝ”๋“œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

<!-- Injects srcset for responsive layout -->
<amp-img
  src="image1.png"
  width="400"
  height="800"
  layout="responsive"
  srcset="image1.470w.png 470w, image1.820w.png 820w, image1.1440w.png 1440w"
></amp-img>
<!-- Ignores existing srcset -->
<amp-img
  layout="fill"
  srcset="image-1x.png 1x,
                               image-2x.png 2x"
></amp-img>

layout=responsive ์‚ฌ์šฉ ์‹œ์—๋Š” ์ตœ์†Œ ์ด๋ฏธ์ง€ ํฌ๊ธฐ๋ฅผ ์ง€์ •ํ•˜๋Š” ๋ฐ width ๋ฐ height ์†์„ฑ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๋ชจ๋ฐ”์ผ์—์„œ ํžˆ์–ด๋กœ ์ด๋ฏธ์ง€๋ฅผ ํ’€ ๋ธ”๋ฆฌ๋“œ๋กœ ํ‘œ์‹œํ•˜๋ ค๋ฉด width=320์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.