TLDR: Это Страница Демонстрирует, как использовать WebAssembly (компилировано из инструмента, называемого PDFCPU с GO V1.12) для извлечения первой страницы файла PDF, выполненного полностью на стороне клиента. Основная идея – добавление поддержки эмуляции файловой системы в среде браузера. Пропустить прозу ниже и пойти прямо в код, см. Github repo Отказ
WebAssembly (WASM) набирает много жужжания в эти дни. Многие языки начинают экспериментировать с/принять эту новую технологию и добавить поддержку сборки WASM. RUST (WASM-PACK, WASM-BINDGEN), GO, C/C ++ (EMSCRIPTEN), Java (Teavm), .NET (Blazor) – это несколько примеров.
В этой статье я пытаюсь документировать мою попытку портировать приложение командной строки, написанное для перехода к WASM и используя его в контексте браузера. Я играл с WASM, используя EMSCRIPTEN для C/C ++ кодов до, но не с Go.
Поддержка WASM для Go приземлилась в V1.11 и во время этого письма новейшая версия выпуска Go – v1.12. Говорят, что поддержка все еще созревает, с некоторыми ограничениями, такими как только Главная Функция теперь экспортируется прямо сейчас (см. Эта проблема ), или не работает на Android из-за проблем с памятью ( Выпуск ). Вещи могут улучшиться и изменять в будущем выпуске, поэтому, пожалуйста, удерживайте это в виду, когда читая шаги ниже.
Кроме того, я не программист в Go (я написал <100 LOC). На самом деле мы даже не собираемся изменить одну линию кода Go в этой статье (но будет предупреждено много вовлеченных JavaScript/Node.js!). Если вы ищете, как звонить в JavaScript от Go в WASM, есть много других выдающихся ресурсов в Интернете для этого, но это не один. Вместо этого это больше о записи JS, чтобы заставить Go WASM запустить с поддержкой файла ввода/вывода в браузере.
Целевое приложение – pdfcpu. , супер полезность для обработки файлов PDF, таких как извлечение страниц, оптимизация размера файла и т. Д. Уже есть тонны онлайн-сайтов для выполнения аналогичных обработок в PDF-файлах без необходимости пользователей для загрузки дополнительного программного обеспечения на своем компьютере, но большинство из них требуют загрузки файла на сторонний сервер, который в некоторых случаях – в зависимости от того, где Сервер находится относительно для вас – время передачи сети (загрузка + загрузка) длиннее, чем фактическое время обработки. Также некоторые документы являются конфиденциальными и загрузки на внешний сервер, не может быть хорошей идеей. Если обработка PDF полностью выполняется в браузере, используя WASM, они станут не проблемами. Кроме того, его можно сделать, чтобы работать полностью в автономном режиме – то есть если вы кэшируете активы страницы в браузере, используя вещи, такие как сервисный работник.
С этим сказанным, давайте начнем.
Самый первый шаг – установить Go Version v1.11 + (для этой статьи) и Node.js используется для этой статьи) и Node.js (я использую версию 12.0.0), которая может быть легко сделана, имея в виду официальные документы – Идти , Node.js Отказ
Следующим шагом является попытка построить нативную бинацию PDFCPU, который снова не сложно, благодаря модулям поддержки модулей для этого проекта. Ссылаясь на Github repo (Примечание: я использую CHARD 9D476DDD92A для этой статьи):
git clone https://github.com/hhrutter/pdfcpu cd pdfcpu/cmd/pdfcpu go build -o pdfcpu
Вы увидите бинарный исполняемый файл pdfcpu В папке работает ./PDFCPU. Версия Выходы PDFCPU версия 0.1.23.
Далее, давайте попробуем построить версию WASM (упомянутую как WASM-модуль), в том же запуске каталога:
GOOS=js GOARCH=wasm go build -o pdfcpu.wasm
Мы увидим скомпилированный файл вывода модуля WASM pdfcpu.wasm.wasm , но как мы знаем, сделает ли это что-нибудь?
От Перейти документацию , можно выполнить файл WASM, используя Node.js. Это требует запуска файла JS под названием wasm_exec.js Расположен в Разное/Wasm Справочник вашей установки GO (например,: /usr/local/go/misc/wasm Примечание. Файл JS должен соответствовать той же версии GO, используемой для компиляции файла WASM, поэтому вы не можете просто захватить последние wasm_exec.js От Github Github repo и ожидайте его работать), поэтому давайте подтвердим, что:
cp /usr/local/go/misc/wasm/wasm_exec.js ./ node wasm_exec.js pdfcpu.wasm version
Выход:
pdfcpu version 0.1.23
Так что действительно файл WASM содержит код PDFCPU.
Давайте давайте запустим его в браузере (PS: браузер, который я использовал для тестирования Chrome), ссылаясь на ту же страницу документации, нам нужно подготовить index.html файл, как это:
Давайте запустим статический файловый сервер для тестирования страницы, но имейте в виду одну вещь, это то, что .wasm Файл должен иметь тип MIME Приложение/Wasm для Webassembly.InstantiateSteStreaming Работать, в противном случае вы получите ошибку, как это в консоли, когда вы посетите index.html :
Uncaught (in promise) TypeError: Failed to execute 'compile' on 'WebAssembly': Incorrect response MIME type. Expected 'application/wasm'.
Я использую этот сценарий Node.js от https://gist.github.com/aolde/8104861 и добавить тип MIME TYME как следует следующее:
....
mimeTypes = {
"html": "text/html",
"jpeg": "image/jpeg",
"jpg": "image/jpeg",
"png": "image/png",
"js": "text/javascript",
"css": "text/css",
"wasm": "application/wasm",
};
.........
Беги с Узел Static_Server.js & и посетить localhost: 8080 На Chrome, затем откройте консоль Devtools, посмотрим:
pdfcpu is a tool for PDF manipulation written in Go.
Usage:
pdfcpu command [arguments]
The commands are:
attachments list, add, remove, extract embedded file attachments
changeopw change owner password
changeupw change user password
decrypt remove password protection
encrypt set password protection
extract extract images, fonts, content, pages, metadata
grid rearrange pages orimages for enhanced browsing experience
import import/convert images
merge concatenate 2 or more PDFs
nup rearrange pages or images for reduced number of pages
optimize optimize PDF by getting rid of redundant page resources
pages insert, remove selected pages
paper print list of supported paper sizes
permissions list, add user access permissions
rotate rotate pages
split split multi-page PDF into several PDFs according to split span
stamp add text, image or PDF stamp to selected pages
trim create trimmed version with selected pages
validate validate PDF against PDF 32000-1:2008 (PDF 1.7)
version print version
watermark add text, image or PDF watermark to selected pages
Completion supported for all commands.
One letter Unix style abbreviations supported for flags.
Use "pdfcpu help [command]" for more information about a command.
Cool, это stdout of Rowing ./PDFCPU. без аргументов
Что, если мы хотим указать аргумент командной строки? Мы можем сделать это по:
// in index.html ... const go = new Go(); go.argv = ['pdfcpu.wasm', 'version']; // <- Add this line ...
Выход в Chrome Console:
pdfcpu version 0.1.23
Теперь давайте попробуем получить pdfcpu Чтобы по-настоящему выполнять работу в файле PDF вместо простота использования/версии/версии для STDOUT, я буду использовать файл спецификации PDF, полученный из https://www.adobe.com/content/dam/acom/en/devnet/pdf/pdfs/pdf_reference_archives/pdfreference.pdf в качестве тестового входного файла.
Прежде чем работать на боковой стороне, давайте посмотрим, как pdfcpu Нативные двоичные исполняемые работы в тестовом файле:
Проверка файла PDF
Извлечь первую страницу
Мы можем сделать то же самое с WASM, используя Node.js (но это занимает гораздо больше времени – около 10x медленнее – по сравнению с родным двоичным)
$ node wasm_exec.js pdfcpu.wasm validate PDFReference.pdf validating(mode=relaxed) PDFReference.pdf ... validation ok $ node wasm_exec.js pdfcpu.wasm trim -pages 1 PDFReference.pdf first_page.pdf pageSelection: 1 trimming PDFReference.pdf ... writing first_page.pdf ...
Как мы могли спросить pdfcpu.wasm Чтобы работать в тестовом файле PDF в браузере? В приведенных выше примерах pdfcpu (Независимо от того, был ли нативный бинарный или модуль WASM BY Node.js), получил путь к файлу PDF Test в качестве аргумента командной строки, и он прочитает байты файла из файловой системы. Но в браузере нет доступа к файловой системе.
Давайте копать глубже в wasm_exec.js Файл Чтобы увидеть, что происходит, когда Node.js запущен модуль WASM, я нахожу следующий фрагмент кода, который представляет интерес:
....
// Map web browser API and Node.js API to a single common API (preferring web standards over Node.js API).
const isNodeJS = global.process && global.process.title === "node";
if (isNodeJS) {
global.require = require;
global.fs = require("fs");
// ..... other
} else {
let outputBuf = "";
global.fs = {
constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused
writeSync(fd, buf) {
outputBuf += decoder.decode(buf);
const nl = outputBuf.lastIndexOf("\n");
if (nl != -1) {
console.log(outputBuf.substr(0, nl));
outputBuf = outputBuf.substr(nl + 1);
}
return buf.length;
},
write(fd, buf, offset, length, position, callback) {
if (offset !== 0 || length !== buf.length || position !== null) {
throw new Error("not implemented");
}
const n = this.writeSync(fd, buf);
callback(null, n);
},
open(path, flags, mode, callback) {
const err = new Error("not implemented");
err.code = "ENOSYS";
callback(err);
},
read(fd, buffer, offset, length, position, callback) {
const err = new Error("not implemented");
err.code = "ENOSYS";
callback(err);
},
fsync(fd, callback) {
callback(null);
},
};
}
....... the rest
Так что мы можем видеть, что если wasm_exec.js управляет Node.js, он может читать из файловой системы, потому что он использует ФС Модуль из Node.js, но если он работает в контексте браузера (остальная ветвь), заглушка для ФС Используется и многие необходимые функции еще не реализованы.
Давайте попробуем это исправить! Есть проект под названием Браузерфы который эмулирует API файловой системы Node.js для браузера, мы будем использовать его вместо ФС заглушка в wasm_exec.js.
В index.html Добавьте тег скрипта в Browserfs файл CDN JS к тегу головки и инициализируйте его, мы также пытаемся написать тестовый файл PDF в inMemory FS (как /test.pdf в FS) и попробуйте запустить утверждать команда /test.pdf. :
Также нужно изменить wasm_exec.js Чтобы использовать браузерфы в контексте браузера:
...
// Map web browser API and Node.js API to a single common API (preferring web standards over Node.js API).
const isNodeJS = global.process && global.process.title === "node";
if (isNodeJS) {
global.require = require;
global.fs = require("fs");
// ..... other
} else {
var myfs = global.BrowserFS.BFSRequire('fs');
global.Buffer = global.BrowserFS.BFSRequire('buffer').Buffer;
global.fs = myfs;
// ... Delete or comment out the original global.fs = {....}
// let outputBuf = "";
}
...
Если мы запустим его, мы видим console.log Позвоните в Fs.readfile Современно сообщайте о байтах содержимого тестового файла, но получаем еще одно загадочное исключение:
Uncaught (in promise) TypeError: Reflect.get called on non-object
at Object.get ()
at syscall/js.valueGet (wasm_exec.js:304)
at syscall_js.valueGet (:8080/wasm-function[1649]:3)
at syscall_js.Value.Get (:8080/wasm-function[1632]:123)
at syscall.init.ializers (:8080/wasm-function[1698]:649)
at syscall.init (:8080/wasm-function[1699]:354)
at os.init (:8080/wasm-function[1817]:299)
at fmt.init (:8080/wasm-function[1884]:328)
at flag.init (:8080/wasm-function[1952]:241)
at main.init (:8080/wasm-function[4325]:247)
Кажется, что время выполнения GO, скомпилированное для WASM, постарается позвонить в землю JS при доступе к Global.fs объект, но что-то не так. Из прослеживания стека исключения не так много полезной информации для отладки.
Сравнивая оригинальную заглушку ФС в wasm_exec.js А что браузер, я заметил, что Константы Недвижимость не определена для Browserfs’s ФС Добавляя это обратно (используя один из Node.js Fs.ConStants , сохраняя только те, которые уставились с O_ ), ошибка уходит:
...
global.fs = myfs;
global.fs.constants = {
O_RDONLY: 0,
O_WRONLY: 1,
O_RDWR: 2,
O_CREAT: 64,
O_EXCL: 128,
O_NOCTTY: 256,
O_TRUNC: 512,
O_APPEND: 1024,
O_DIRECTORY: 65536,
O_NOATIME: 262144,
O_NOFOLLOW: 131072,
O_SYNC: 1052672,
O_DIRECT: 16384,
O_NONBLOCK: 2048,
};
Но мы получим еще одну ошибку:
exit code: 1
Похоже, указывается, что что-то пошло не так, и программа выходит с выходом с кодом 1, аналогично тому, что произойдет в оболочке.
Мы все еще можем работать над чем-то. Одна из причин, что есть такой маленький журнал – это то, что оригинальная заглушка Global.fs в wasm_exec.js Содержит console.log Звонит, что я думаю, отвечает за регистрацию STDOUT/STDERR модуля WASM, но реализация Browserfs не поддерживает это, поэтому мы проверяем на FD передан на Fs.write. / Fs.writeync. , если FD 1 или 2 (соответствует STDOUT/STDERR), мы используем оригинальную функцию заглушки, в противном случае мы призываем к браузернам
// ... Add to wasm_exec.js, below the global.fs.constants = {...} mentioned above
let outputBuf = "";
global.fs.writeSyncOriginal = global.fs.writeSync;
global.fs.writeSync = function(fd, buf) {
if (fd === 1 || fd === 2) {
outputBuf += decoder.decode(buf);
const nl = outputBuf.lastIndexOf("\n");
if (nl != -1) {
console.log(outputBuf.substr(0, nl));
outputBuf = outputBuf.substr(nl + 1);
}
return buf.length;
} else {
return global.fs.writeSyncOriginal(...arguments);
}
};
global.fs.writeOriginal = global.fs.write;
global.fs.write = function(fd, buf, offset, length, position, callback) {
if (fd === 1 || fd === 2) {
if (offset !== 0 || length !== buf.length || position !== null) {
throw new Error("not implemented");
}
const n = this.writeSync(fd, buf);
callback(null, n, buf);
} else {
return global.fs.writeOriginal(...arguments);
}
};
После добавления этого мы сейчас получаем:
validating(mode=relaxed) /test.pdf ... wasm_exec.js:89 can't open "/test.pdf": open /test.pdf: Invalid argument wasm_exec.js:135 exit code: 1 exit @ wasm_exec.js:135 runtime.wasmExit @ wasm_exec.js:269 runtime.wasmExit @ wasm-020eb99a-871:3 runtime.exit @ wasm-020eb99a-860:2 syscall.Exit @ wasm-020eb99a-579:26 os.Exit @ wasm-020eb99a-1802:65 main.process @ wasm-020eb99a-4283:215 main.main @ wasm-020eb99a-4281:591 runtime.main @ wasm-020eb99a-466:673 ...
Теперь у нас есть некоторые прогресс, STDOUT/STDERR снова работает, и мы увидели ошибку «Неверный аргумент».
Я застрял на этой части на некоторое время, но позже найди выход.
Помните модуль WASM на Node.js Wasm пробежать просто хорошо? Не должно быть некоторая разница между двумя реализацией ФС (Node.js One and Browserfs), мы можем использовать это в качестве отправной точки для устранения неполадок.
Мы можем использовать прокси в JavaScript для печати аргументов функции и возвращаемое значение всякий раз, когда функция в ФС Модуль вызывается, добавив эти строки в wasm_exec.js. :
.....
var handler = {
get: function(target, property) {
if(property in target && target[property] instanceof Function) {
return function() {
console.log(property, 'called', arguments);
if (arguments[arguments.length - 1] instanceof Function) {
var origCB = arguments[arguments.length - 1];
var newCB = function() {
console.log('callback for', property, 'get called with args:', arguments);
return Reflect.apply(origCB, arguments.callee, arguments);
}
arguments[arguments.length - 1] = newCB;
}
return Reflect.apply(target[property], target, arguments);
}
} else {
return target[property]
}
}
}
// Map web browser API and Node.js API to a single common API (preferring web standards over Node.js API).
const isNodeJS = global.process && global.process.title === "node";
if (isNodeJS) {
global.require = require;
var myfs = require("fs");
global.fs = new Proxy(myfs, handler); // <- "install" the handler for proxy
// ... the rest
} eles {
var myfs = global.BrowserFS.BFSRequire('fs');
global.Buffer = global.BrowserFS.BFSRequire('buffer').Buffer;
// ..... the previous global.fs.constants = {...}, global.fs.write = function (...) {...}
global.fs = new Proxy(global.fs, handler); // <- "install" the handler for proxy;
}
Теперь запустите Node.js снова с stdbuf -o 0 узла wasm_exec.js pdfcpu.wasm.wasmyteate pdfreference.pdf | Tee Trace.log
Мы получим много выходов, детализирующих каждый звонок на ФС Модуль с аргументами и возвращаемым значением (к обратным вызовам), как использовать поспешно :
.....
open called { '0': 'PDFReference.pdf', '1': 0, '2': 0, '3': [Function] }
callback for open get called with args: { '0': null, '1': 11 }
fstat called { '0': 11, '1': [Function] }
callback for fstat get called with args: { '0': null,
'1':
Stats {
dev: 1275115201,
mode: 33204,
nlink: 1,
uid: 1000,
gid: 1000,
rdev: 0,
blksize: 4096,
ino: 3889238,
size: 5158704,
blocks: 10080,
atimeMs: 1555990816488.329,
mtimeMs: 1555987073908.2253,
ctimeMs: 1555987073908.2253,
birthtimeMs: 1555987073908.2253,
atime: 2019-04-23T03:40:16.488Z,
mtime: 2019-04-23T02:37:53.908Z,
ctime: 2019-04-23T02:37:53.908Z,
birthtime: 2019-04-23T02:37:53.908Z } }
fstat called { '0': 11, '1': [Function] }
callback for fstat get called with args: { '0': null,
'1':
Stats {
dev: 1275115201,
mode: 33204,
nlink: 1,
uid: 1000,
gid: 1000,
rdev: 0,
blksize: 4096,
ino: 3889238,
size: 5158704,
blocks: 10080,
.....
Бег на браузере мы получаем ошибку в некотором вызове:
open called Arguments(4) ["/test.pdf", 0, 0, ƒ, callee: ƒ, Symbol(Symbol.iterator): ƒ]
callback for open get called with args: Arguments [ApiError, callee: ƒ, Symbol(Symbol.iterator): ƒ]
0: ApiError
code: "EINVAL"
errno: 22
message: "Error: EINVAL: Invalid flag: 0"
path: undefined
stack: "Error
at new ApiError (https://cdnjs.cloudflare.com/ajax/libs/BrowserFS/2.0.0/browserfs.js:5430:22)
at new FileFlag (https://cdnjs.cloudflare.com/ajax/libs/BrowserFS/2.0.0/browserfs.js:5551:15)
at Function.getFileFlag (https://cdnjs.cloudflare.com/ajax/libs/BrowserFS/2.0.0/browserfs.js:5565:42)
at FS.open (https://cdnjs.cloudflare.com/ajax/libs/BrowserFS/2.0.0/browserfs.js:6103:69)
at Object._fsMock. [as open] (https://cdnjs.cloudflare.com/ajax/libs/BrowserFS/2.0.0/browserfs.js:7006:28)
at Proxy. (http://localhost:8080/wasm_exec.js:29:37)
at syscall/js.valueCall (http://localhost:8080/wasm_exec.js:371:31)
at syscall_js.valueCall (wasm-function[1653]:3)
at syscall_js.Value.Call (wasm-function[1636]:482)
at syscall.fsCall (wasm-function[1691]:666)"
syscall: ""
__proto__: Error
callee: ƒ ()
length: 1
Symbol(Symbol.iterator): ƒ values()
__proto__: Object
Таким образом, время выполнения WASM проходит значение Browserfs, что он не принимает (2-й параметр функции fscen Функция Флаги В этом случае 0 проходит 0), копал исходный код, похоже, что браузерс открыть Функция может принимать только строку для аргумента Флаги («R», «W», «W +» и т. Д.), чтобы мы могли вручную преобразовать это в wasm_exec.js :
(Ref: https://nodejs.org/api/fs.html#fs_file_system_flags )
global.fs.openOriginal = global.fs.open;
global.fs.open = function(path, flags, mode, callback) {
var myflags = 'r';
var O = global.fs.constants;
// Convert numeric flags to string flags
// FIXME: maybe wrong...
if (flags & O.O_WRONLY) { // 'w'
myflags = 'w';
if (flags & O.O_EXCL) {
myflags = 'wx';
}
} else if (flags & O.O_RDWR) { // 'r+' or 'w+'
if (flags & O.O_CREAT && flags & O.O_TRUNC) { // w+
if (flags & O.O_EXCL) {
myflags = 'wx+';
} else {
myflags = 'w+';
}
} else { // r+
myflags = 'r+';
}
} else if (flags & O.O_APPEND) { // 'a'
throw "Not implmented"
}
// TODO: handle other cases
return global.fs.openOriginal(path, myflags, mode, callback);
};
Берегите это, мы получаем некоторую прогресс, но в конечном итоге с новой ошибкой:
Uncaught (in promise) TypeError: Cannot read property 'get' of undefined
at storeValue (wasm_exec.js:245)
at syscall/js.valueCall (wasm_exec.js:388)
at syscall_js.valueCall (:8080/wasm-function[1653]:3)
at syscall_js.Value.Call (:8080/wasm-function[1636]:482)
at syscall.fsCall (:8080/wasm-function[1691]:666)
at syscall.Close (:8080/wasm-function[1682]:399)
at internal_poll.__FD_.destroy (:8080/wasm-function[1771]:215)
at internal_poll.__FD_.decref (:8080/wasm-function[1768]:212)
at internal_poll.__FD_.Close (:8080/wasm-function[1772]:282)
at os.__file_.close (:8080/wasm-function[1799]:224)
Если сравнивать trace.log (Node.js) с выходом консоли (Browserfs), мы можем заметить, что Статус Объект передан обратным вызове Fs.fstat Разное, так что снова мы вручную «патч», что в wasm_exec.js :
global.fs.fstatOriginal = global.fs.fstat;
global.fs.fstat = function(fd, callback) {
return global.fs.fstatOriginal(fd, function() {
var retStat = arguments[1];
delete retStat['fileData'];
retStat.atimeMs = retStat.atime.getTime();
retStat.mtimeMs = retStat.mtime.getTime();
retStat.ctimeMs = retStat.ctime.getTime();
retStat.birthtimeMs = retStat.birthtime.getTime();
return callback(arguments[0], retStat);
});
};
Продолжая, есть много звонок в читать Теперь и, наконец, вывод
validation ok
Потрясающие, поэтому наши браузерны + подход для исправлений работает!
Далее, давайте попробуем что-то, что выписывали некоторые данные – извлекать первую страницу PDF в first_page.pdf (см. go.argv ниже), в index.html :
function done() {
const go = new Go();
WebAssembly.instantiateStreaming(fetch("pdfcpu.wasm"), go.importObject).then((result) => {
go.argv = ['pdfcpu.wasm', 'trim', '-pages', '1', '/test.pdf', '/first_page.pdf'];
var st = Date.now();
go.run(result.instance);
console.log('Time taken:', Date.now() - st);
fs.readFile('/first_page.pdf', function(err, contents) {
console.log("after run main:", err, contents);
});
});
}
Это дает еще одну ошибку:
callback for writeOriginal get called with args:
TypeError: buffer$$1.copy is not a function
at SyncKeyValueFile.writeSync (https://cdnjs.cloudflare.com/ajax/libs/BrowserFS/2.0.0/browserfs.js:8560:29)
at SyncKeyValueFile.write (https://cdnjs.cloudflare.com/ajax/libs/BrowserFS/2.0.0/browserfs.js:8523:27)
at FS.write (https://cdnjs.cloudflare.com/ajax/libs/BrowserFS/2.0.0/browserfs.js:6386:14)
at Object._fsMock. [as writeOriginal] (https://cdnjs.cloudflare.com/ajax/libs/BrowserFS/2.0.0/browserfs.js:7006:28)
at Proxy. (http://localhost:8080/wasm_exec.js:29:37)
at Object.global.fs.write (http://localhost:8080/wasm_exec.js:108:34)
at Proxy. (http://localhost:8080/wasm_exec.js:29:37)
at syscall/js.valueCall (http://localhost:8080/wasm_exec.js:406:31)
at syscall_js.valueCall (wasm-function[1653]:3)
at syscall_js.Value.Call (wasm-function[1636]:482)
Мы находим, что Buf передан на Fs.write нет Скопировать метод. Итак, мы изменим это:
global.fs.write = function(fd, buf, offset, length, position, callback) {
if (fd === 1 || fd === 2) {
if (offset !== 0 || length !== buf.length || position !== null) {
throw new Error("not implemented");
}
const n = this.writeSync(fd, buf);
callback(null, n, buf);
} else {
// buf:
arguments[1] = global.Buffer.from(arguments[1]);
return global.fs.writeOriginal(...arguments);
}
};
Наконец мы получаем байты первой страницы в консольном журнале! (Вы можете взглянуть на файл Oldindex.html в репо для кода до этого момента)
Теперь, когда он работает нормально (по крайней мере, для двух случаев, которые мы проверили, для других случаев мы можем использовать тот же метод сравнения реализации Browserfs с помощью вывода Node.js и Patch global.fs. XXX в wasm_exec.js ). Пользователь загружает обработанные файлы PDF в браузере.
Вы можете посмотреть на демо-страницу здесь
Заключение:
Нам удалось сделать некоторые хаки на wasm_exec.js Сочетание с Browserfs, чтобы запустить утилиту командной строки GO в браузере. По мере того, как WASM поддерживает более зрело, может быть официальная поддержка эмуляции файловой системы в браузере (похоже на emscripten) в будущем, или будет поддержка определенной функции экспорта в модуле WASM, который позволяет непосредственно работать на байтах вместо того, чтобы прыгать через обручи файла O/o.
Если вы хотите увидеть последний код, пожалуйста, перейдите к Github repo Отказ
Вы также можете проверить мои другие проекты в https://github.com/wcchoi.
Проблемы:
Я заявил в самом начале, что WASM можно было использовать на стороне клиента, чтобы заменить некоторую обработку файлов на сервере, но этот подход не без его проблем:
Большой размер модуля Webassembly
- Если мы подключаемся к localhost, это не проблема, но
pdfcpu.wasmРазмер 8mmib, который очень большим и поражает заявленное преимущество меньшем трансфера сети (загрузка + загрузка) по сравнению с загрузкой на внешний сервер. - Это может быть решено
Гжипфайл WASM или лучше использоватьбротисжать, в моем тесте,ГЦИП-9Уменьшите размер файла до 1.8MIB иBrotli -9до 1,5 минда, намного меньше, чем несжатый - Если он по-прежнему слишком большой, мы можем вручную изменить код GO, чтобы разделить функции в индивидуальные инструменты командной строки (один инструмент для слияния, другой для разделения PDF, и т. Д.), затем отдельно компилируйте в WASM и загружать только модуль WASM для конкретная задача пользовательских запросов
- Если мы подключаемся к localhost, это не проблема, но
Медленное исполнение по сравнению с родным
- При использовании нативного двоичного двоика, на одном конкретном тесте (извлечение первой страницы файла PDF 5MIB), время обработки составляет всего 1 с, но с использованием Node.js и Wasm, это 15s, 15x медленнее
- В браузере это примерно так же: 13-14
- Таким образом, иногда может быть еще быстрее просто загружать на мощный сервер для обработки, даже приму время, необходимое для загрузки/загрузки файлов.
- Также машина клиента может быть удовлетворена ресурсами, и не может обработать большие файлы в браузере (вкладка просто будет сбиться, если это произойдет)
- Но очень вероятно, что WASM Runtime браузера будет становится быстрее, а Backenc Go Compiler Target Backend генерирует лучшее/более быстрый код в будущем
- Прямо сейчас я не знаю ни о каком инструменте профилирования для Wasm, чтобы понять, почему он медленнее, но, используя вкладку «Источник» в DEAVTOOLS Chrome’s Devtools и нажимающий «Пауза выполнения сценария» случайным образом, я замечаю, что много раз останавливается на функциях, которые (возможно,? ) Связанные с распределением памяти или сборки мусора, возможно, когда поддержка GC для WASM поступает в будущем, все будет быстрее.
Соответствующие проекты:
Есть много библиотек, которые уже используют PDF в браузере, некоторые с использованием порта EMSCRIPTEN библиотек C/C ++, другие в чистое JS. Если у вас такая потребность в вашем проекте, вот несколько примеров:
- https://github.com/DevelopingMagic/pdfassembler
- https://github.com/jrmuizel/qpdf.js
- https://github.com/manuels/unix-toolbox.js-poppler
Оригинал: “https://dev.to/wcchoi/browser-side-pdf-processing-with-go-and-webassembly-13hn”