국제화 호출 속도를 최대 5-1000배 향상
문맥
모든 것은 2년 전에 시작되었습니다. 나는 다른 언어를 처리하기 위해 i18n 모듈이 필요한 처음부터 작성된 대규모 소셜 네트워크를 위한 새로운 PWA를 작업하고 있었습니다. 모듈은 다음을 수행해야 했습니다.
가벼워야 합니다(PWA이므로 제한된 대역폭으로 실행해야 함).
빠르게 실행합니다(일부 사용자는 저사양 기기를 사용했습니다).
그리고 그것이 소름 끼치는 곳입니다. 가능한 유일한 라이브러리는 Google Closure MessageFormat이었습니다. 저가형 장치에서는 그렇게 빠르지 않았고 번들에 큰 영향을 미쳤습니다. 그래서 저는 성능을 염두에 두고 저만의 글을 쓰기로 했습니다.
현재까지도 문제는 여전히 i18n 라이브러리와 동일하므로 다른 것보다 5~1000배 빠른 1kb i18n 라이브러리를 오픈 소스💋Frenchkiss.js로 공개했습니다.
성능 최적화에 대한 여정을 저와 함께 하세요.
👉 Time to speed up your webapp for mobile devices!
🤷 i18n 모듈은 어떻게 작동하나요?
내부적으로는 일부 i18n 모듈이 호출할 때마다 변환을 다시 처리하여 성능이 저하됩니다.
다음은 번역 기능 내에서 발생할 수 있는 일의 예입니다(Polyglot.js의 정말 단순화된 순진한 버전).
const applyParams = (text, params = {}) => {
// Apply plural if exists
const list = text.split('||||');
const pluralIndex = getPluralIndex(params.count);
const output = list[pluralIndex] || list[0];
// Replace interpolation
return output.replace(/%\{\s*(\w+)\s*\}/g, ($0, $1) => params[$1] || '');
}
applyParams('Hello %{name} !', {
name: 'John'
});
// => Hello John !
즉, 각 번역 호출에서 텍스트를 분할하고, 복수형 인덱스를 계산하고, RegExp를 만들고, 지정된 매개변수가 있는 경우 지정된 매개변수로 모든 항목을 바꾸고 결과를 반환합니다.
그다지 큰 문제는 아니지만 각 렌더링/필터/지시 호출에서 여러 번 수행해도 괜찮습니까?
👉 It's one of the first things we learn when building app in react, angular, vuejs or any other framework : avoid intensive operations inside render methods, filters and directives, it will kill your app !
일부 i18n 라이브러리가 더 잘 작동하고 있습니다!
다른 사람들은 상황을 상당히 최적화하고 있습니다. 예를 들어 Angular, VueJs-i18n, Google Closure가 있습니다.
그들은 그것을 어떻게 하고 있습니까? 실제로 그들은 문자열을 한 번만 구문 분석하고 다음 호출에서 처리하기 위해 opcode 목록을 캐시합니다.
opcode에 익숙하지 않은 경우 기본적으로 처리할 지침 목록이며 이 경우 번역을 작성하기 위한 것입니다. 다음은 번역에서 생성된 opcode의 가능한 예입니다.
[{
"type": "text",
"value": "Hello "
}, {
"type": "variable",
"value": "name"
}, {
"type": "text",
"value": " !"
}]
결과를 인쇄하는 방법:
const printOpcode = opcodes => opcodes.map(code => (
(code.type === 'text') ? code.value :
(code.type === 'variable') ? (params[code.value] || '') :
(code.type === 'select') ? printOpCode( // recursive
params.data[params[code.value]] || params.data.other
) :
(code.type === 'plural') ? printOpCode( // recursive
params.list[getPluralIndex(params[code.value])] || params.list[0]
) :
'' // TODO not supported ?
)).join('');
이 유형의 알고리즘을 사용하면 opcode를 생성하는 첫 번째 호출에 더 많은 시간이 할당되지만 저장하고 다음 호출에서 더 빠른 성능을 위해 재사용합니다.
const applyParams = (text, params = {}) => {
// Apply plural if exists
const list = text.split('||||');
const pluralIndex = getPluralIndex(params.count);
const output = list[pluralIndex] || list[0];
// Replace interpolation
return output.replace(/%\{\s*(\w+)\s*\}/g, ($0, $1) => params[$1] || '');
}
applyParams('Hello %{name} !', {
name: 'John'
});
// => Hello John !
👉 It's one of the first things we learn when building app in react, angular, vuejs or any other framework : avoid intensive operations inside render methods, filters and directives, it will kill your app !
[{
"type": "text",
"value": "Hello "
}, {
"type": "variable",
"value": "name"
}, {
"type": "text",
"value": " !"
}]
const printOpcode = opcodes => opcodes.map(code => (
(code.type === 'text') ? code.value :
(code.type === 'variable') ? (params[code.value] || '') :
(code.type === 'select') ? printOpCode( // recursive
params.data[params[code.value]] || params.data.other
) :
(code.type === 'plural') ? printOpCode( // recursive
params.list[getPluralIndex(params[code.value])] || params.list[0]
) :
'' // TODO not supported ?
)).join('');
글쎄요! 그러나 더 나아가는 것이 가능합니까?
🤔 어떻게 속도를 높일 수 있을까요?
💋Frenchkiss.js은 한 단계 더 나아가 번역을 네이티브 함수로 컴파일합니다. 이 함수는 매우 가볍고 순수하여 Javascript가 쉽게 처리할 수 있습니다JIT compile it.
어떻게 작동합니까?
매우 간단합니다. 실제로 다음을 수행하는 문자열에서 함수를 작성할 수 있습니다.
const sum = new Function('a', 'b', 'return a + b');
sum(5, 3);
// => 8
For further informations, take a look at Function Constructor (MDN).
주요 논리는 여전히 opcode 목록을 생성하는 것이지만 번역을 생성하는 데 사용하는 대신 추가 프로세스 없이 번역을 반환하는 최적화된 함수를 생성하는 데 사용합니다.
실제로 가능한 것은 간단한 보간 구조와 SELECT/PLUTAL 표현식 때문입니다. 기본적으로 일부 삼항으로 반환됩니다.
const opCodeToFunction = (opcodes) => {
const output = opcodes.map(code => (
(code.type === 'text') ? escapeText(code.value) :
(code.type === 'variable') ? `params[${code.value}]` :
(code.type === 'select') ? ... :
(code.type === 'plural') ? ... :
'' // TODO Something wrong happened (invalid opcode)
));
// Fallback for empty string if no data;
const result = output.join('+') || "";
// Generate the function
return new Function(
'arg0',
'arg1',
`
var params = arg0 || {};
return ${result};
`);
});
⚠️ 참고: 동적 함수를 빌드할 때 사용자 입력을 이스케이프 처리하여 피하십시오XSS injection!
더 이상 고민하지 않고 생성된 함수를 살펴보겠습니다(참고: 실제 생성된 함수는 조금 더 복잡하지만 아이디어를 얻을 수 있습니다).
보간 생성 함수
// "Hello {name} !"
function generated (params = {}) {
return 'Hello ' + (params.name || '') + ' !';
}
By default, we still fallback to empty string to avoid printing "undefined" as plain text.
표현식 생성 함수 선택
// "Check my {pet, select, cat{evil cat} dog{good boy} other{{pet}}} :D"
function generated (params = {}) {
return 'Check my ' + (
(params.pet == 'cat') ? 'evil cat' :
(params.pet == 'dog') ? 'good boy' :
(params.pet || '')
) + ' :D';
}
We don't use strict equality to keep supports for numbers.
복수형 생성 함수
// "Here {N, plural, =0{nothing} few{few} other{some}} things !"
function generated (params = {}, plural) {
const safePlural = plural ? { N: plural(params.N) } :{};
return 'Here ' + (
(params.N == '0') ? 'nothing' :
(safePlural.N == 'few') ? 'few' :
'some'
) + ' things !';
}
We cache the plural category to avoid re-fetching it in case of multiple checks.
🚀 결론
생성된 함수를 사용하여 우리는 다른 것보다 5배에서 1000배 더 빠르게 코드를 실행할 수 있었으며, 중요한 경로를 렌더링할 때 RegExp, 분할, 맵 작업을 수행하지 않고 가비지 수집기 일시 중지도 방지했습니다.
마지막으로 가장 좋은 소식은 GZIP 크기가 1kB에 불과하다는 것입니다!
If you're searching for a i18n javascript library to accelerate your PWA, or your SSR, you should probably give 💋Frenchkiss.js a try !
Reference
이 문제에 관하여(국제화 호출 속도를 최대 5-1000배 향상), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://dev.to/vince_tblt/speed-up-your-internationalization-calls-up-to-5-1000-times-1778
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
const sum = new Function('a', 'b', 'return a + b');
sum(5, 3);
// => 8
For further informations, take a look at Function Constructor (MDN).
const opCodeToFunction = (opcodes) => {
const output = opcodes.map(code => (
(code.type === 'text') ? escapeText(code.value) :
(code.type === 'variable') ? `params[${code.value}]` :
(code.type === 'select') ? ... :
(code.type === 'plural') ? ... :
'' // TODO Something wrong happened (invalid opcode)
));
// Fallback for empty string if no data;
const result = output.join('+') || "";
// Generate the function
return new Function(
'arg0',
'arg1',
`
var params = arg0 || {};
return ${result};
`);
});
// "Hello {name} !"
function generated (params = {}) {
return 'Hello ' + (params.name || '') + ' !';
}
By default, we still fallback to empty string to avoid printing "undefined" as plain text.
// "Check my {pet, select, cat{evil cat} dog{good boy} other{{pet}}} :D"
function generated (params = {}) {
return 'Check my ' + (
(params.pet == 'cat') ? 'evil cat' :
(params.pet == 'dog') ? 'good boy' :
(params.pet || '')
) + ' :D';
}
We don't use strict equality to keep supports for numbers.
// "Here {N, plural, =0{nothing} few{few} other{some}} things !"
function generated (params = {}, plural) {
const safePlural = plural ? { N: plural(params.N) } :{};
return 'Here ' + (
(params.N == '0') ? 'nothing' :
(safePlural.N == 'few') ? 'few' :
'some'
) + ' things !';
}
We cache the plural category to avoid re-fetching it in case of multiple checks.
생성된 함수를 사용하여 우리는 다른 것보다 5배에서 1000배 더 빠르게 코드를 실행할 수 있었으며, 중요한 경로를 렌더링할 때 RegExp, 분할, 맵 작업을 수행하지 않고 가비지 수집기 일시 중지도 방지했습니다.
마지막으로 가장 좋은 소식은 GZIP 크기가 1kB에 불과하다는 것입니다!
If you're searching for a i18n javascript library to accelerate your PWA, or your SSR, you should probably give 💋Frenchkiss.js a try !
Reference
이 문제에 관하여(국제화 호출 속도를 최대 5-1000배 향상), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/vince_tblt/speed-up-your-internationalization-calls-up-to-5-1000-times-1778텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)