removed everything not nano-related, rewrote the logic of serial connection so the user is not prompted to connect 2 times when using the old bootloader

This commit is contained in:
Oleksiy H
2025-11-09 15:23:46 +02:00
parent 5888eee2db
commit 0c0b6d852f
14 changed files with 1302 additions and 1503 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2348
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +1,13 @@
{ {
"name": "web-arduino-uploader", "name": "arduino-nano-web-uploader",
"version": "1.1.2", "version": "1.1.2",
"main": "dist/index.ts", "main": "dist/index.ts",
"types": "dist/index.d.ts", "types": "dist/index.d.ts",
"license": "MIT", "license": "MIT",
"author": { "author": {
"name": "David Buezas", "name": "Oleksiy Hrachov",
"email": "david.buezas@gmail.com", "email": "oleksiy.h@proton.me",
"url": "https://github.com/dbuezas/arduino-web-uploader/" "url": "https://git.pinkduck.xyz/oleksiy/arduino-NANO-web-uploader"
}, },
"scripts": { "scripts": {
"prepublish": "npm run build", "prepublish": "npm run build",
@ -23,8 +23,8 @@
"process": "^0.11.10", "process": "^0.11.10",
"stream-browserify": "^3.0.0", "stream-browserify": "^3.0.0",
"typescript": "^4.0.3", "typescript": "^4.0.3",
"webpack": "^5.2.0", "webpack": "^5.90.0",
"webpack-cli": "^4.1.0" "webpack-cli": "^5.1.4"
}, },
"dependencies": { "dependencies": {
"async": "^3.2.0", "async": "^3.2.0",

View File

@ -44,6 +44,50 @@ export class Serial {
port?: Port port?: Port
reader?: NodeJS.ReadableStream reader?: NodeJS.ReadableStream
writer?: WritableStreamDefaultWriter writer?: WritableStreamDefaultWriter
private rwStream?: NodeJS.ReadWriteStream
// ADD THIS METHOD
async requestPort(portFilters: PortFilters = {}): Promise<Port> {
const port = await navigator.serial.requestPort(portFilters)
return port
}
// ADD THIS METHOD
async openPort(port: Port, options: SerialOptions): Promise<void> {
const fullOptions = {
baudRate: 9600,
dataBits: 8,
stopBits: 1,
parity: 'none',
bufferSize: 255,
rtscts: false,
xon: false,
xoff: false,
...options,
}
if (this.port) await this.close()
this.port = port
await this.port.open(fullOptions)
this.reader = new ReadableWebToNodeStream(this.port.readable)
this.writer = this.port.writable.getWriter()
// Create and store the stream
const rwStream = (this.reader as unknown) as NodeJS.ReadWriteStream
// @ts-ignore
rwStream.write = (buffer: string | Uint8Array, onDone: (err: Error | null | undefined) => void) => {
this.writer!.write(buffer).then(() => onDone(null), onDone)
return true
}
this.rwStream = rwStream
}
// ADD THIS METHOD
getStream(): NodeJS.ReadWriteStream {
if (!this.rwStream) throw new Error('Not connected')
return this.rwStream
}
async close() { async close() {
if (this.reader) { if (this.reader) {
@ -68,38 +112,13 @@ export class Serial {
async connectWithPaired(options: SerialOptions) { async connectWithPaired(options: SerialOptions) {
const [port] = await navigator.serial.getPorts() const [port] = await navigator.serial.getPorts()
if (!port) throw new Error('no paired') if (!port) throw new Error('no paired')
return this._connect(options, port) await this.openPort(port, options)
return this.getStream()
} }
async connect(options: SerialOptions, portFilters: PortFilters = {}) { async connect(options: SerialOptions, portFilters: PortFilters = {}) {
const port = await navigator.serial.requestPort(portFilters) const port = await this.requestPort(portFilters)
return this._connect(options, port) await this.openPort(port, options)
} return this.getStream()
async _connect(options: SerialOptions, port: Port) {
options = {
baudRate: 9600,
dataBits: 8,
stopBits: 1,
parity: 'none',
bufferSize: 255,
rtscts: false,
xon: false,
xoff: false,
...options,
}
if (this.port) await this.close()
this.port = port
await this.port.open(options)
this.reader = new ReadableWebToNodeStream(this.port.readable)
this.writer = this.port.writable.getWriter()
// next I'm faking a NodeJS.ReadWriteStream
const rwStream = (this.reader as unknown) as NodeJS.ReadWriteStream
// @ts-ignore
rwStream.write = (buffer: string | Uint8Array, onDone: (err: Error | null | undefined) => void) => {
this.writer!.write(buffer).then(() => onDone(null), onDone)
return true
}
return rwStream
} }
} }
const serial = new Serial() const serial = new Serial()

View File

@ -11,39 +11,15 @@ type Board = {
baudRate: number baudRate: number
use_8_bit_addresseses?: boolean use_8_bit_addresseses?: boolean
} }
export const boards = { export const boards = {
avr4809: {
signature: Buffer.from([0x1e, 0x96, 0x51]),
pageSize: 128,
timeout: 400,
baudRate: 115200,
use_8_bit_addresseses: true,
} as Board,
lgt8f328p: {
signature: Buffer.from([0x1e, 0x95, 0x0f]),
pageSize: 128,
timeout: 400,
baudRate: 57600,
} as Board,
nanoOldBootloader: { nanoOldBootloader: {
signature: Buffer.from([0x1e, 0x95, 0x0f]), signature: Buffer.from([0x1e, 0x95, 0x0f]),
pageSize: 128, pageSize: 128,
timeout: 400, timeout: 400,
baudRate: 57600, baudRate: 57600,
} as Board, } as Board,
nano: { nanoNewBootloader: {
signature: Buffer.from([0x1e, 0x95, 0x0f]),
pageSize: 128,
timeout: 400,
baudRate: 115200,
} as Board,
uno: {
signature: Buffer.from([0x1e, 0x95, 0x0f]),
pageSize: 128,
timeout: 400,
baudRate: 115200,
} as Board,
proMini: {
signature: Buffer.from([0x1e, 0x95, 0x0f]), signature: Buffer.from([0x1e, 0x95, 0x0f]),
pageSize: 128, pageSize: 128,
timeout: 400, timeout: 400,
@ -53,9 +29,46 @@ export const boards = {
const noop = (callback: () => void) => callback() const noop = (callback: () => void) => callback()
console.log("Arduino Web Uploader Version:", version) //console.log("Nano Web Uploader Version:", version)
export async function upload(
async function attemptUpload(
serialStream: any,
hex: Buffer,
board: Board, board: Board,
verify: boolean,
onProgress: (percentage: number) => void
) {
onProgress(0)
const stk500 = new Stk500()
let sent = 0
let total = hex.length / board.pageSize
if (verify) total *= 2
stk500.log = (what: string) => {
if (what === 'page done' || what === 'verify done') {
sent += 1
const percent = Math.round((100 * sent) / total)
onProgress(percent)
}
console.log(what, sent, total, hex.length, board.pageSize)
}
await async.series([
stk500.sync.bind(stk500, serialStream, 3, board.timeout),
stk500.sync.bind(stk500, serialStream, 3, board.timeout),
stk500.sync.bind(stk500, serialStream, 3, board.timeout),
stk500.verifySignature.bind(stk500, serialStream, board.signature, board.timeout),
stk500.setOptions.bind(stk500, serialStream, {}, board.timeout),
stk500.enterProgrammingMode.bind(stk500, serialStream, board.timeout),
stk500.upload.bind(stk500, serialStream, hex, board.pageSize, board.timeout, board.use_8_bit_addresseses),
!verify ? noop : stk500.verify.bind(stk500, serialStream, hex, board.pageSize, board.timeout, board.use_8_bit_addresseses),
stk500.exitProgrammingMode.bind(stk500, serialStream, board.timeout),
])
}
export async function upload(
hexFileHref: string, hexFileHref: string,
onProgress: (percentage: number) => void, onProgress: (percentage: number) => void,
verify = false, verify = false,
@ -66,34 +79,21 @@ export async function upload(
.then((response) => response.text()) .then((response) => response.text())
let { data: hex } = intel_hex.parse(text) let { data: hex } = intel_hex.parse(text)
const serialStream = await serial.connect({ baudRate: board.baudRate }, portFilters) const port = await serial.requestPort(portFilters)
onProgress(0)
const stk500 = new Stk500() try {
let sent = 0 await serial.openPort(port, { baudRate: boards.nanoNewBootloader.baudRate })
let total = hex.length / board.pageSize const serialStream = serial.getStream()
if (verify) total *= 2 await attemptUpload(serialStream, hex, boards.nanoNewBootloader, verify, onProgress)
stk500.log = (what: string) => { } catch (error) {
if (what === 'page done' || what === 'verify done') { await serial.close()
sent += 1 await new Promise(resolve => setTimeout(resolve, 500))
const percent = Math.round((100 * sent) / total)
onProgress(percent) await serial.openPort(port, { baudRate: boards.nanoOldBootloader.baudRate })
} const serialStream = serial.getStream()
console.log(what, sent, total, hex.length, board.pageSize) await attemptUpload(serialStream, hex, boards.nanoOldBootloader, verify, onProgress)
} }
await async.series([
// send two dummy syncs like avrdude does
stk500.sync.bind(stk500, serialStream, 3, board.timeout),
stk500.sync.bind(stk500, serialStream, 3, board.timeout),
stk500.sync.bind(stk500, serialStream, 3, board.timeout),
stk500.verifySignature.bind(stk500, serialStream, board.signature, board.timeout),
stk500.setOptions.bind(stk500, serialStream, {}, board.timeout),
stk500.enterProgrammingMode.bind(stk500, serialStream, board.timeout),
stk500.upload.bind(stk500, serialStream, hex, board.pageSize, board.timeout, board.use_8_bit_addresseses),
!verify ? noop : stk500.verify.bind(stk500, serialStream, hex, board.pageSize, board.timeout, board.use_8_bit_addresseses),
stk500.exitProgrammingMode.bind(stk500, serialStream, board.timeout),
])
} finally { } finally {
serial.close() serial.close()
} }

View File

@ -1,44 +0,0 @@
import { upload, boards } from '.'
import { PortFilters } from './Serial'
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('[arduino-uploader]').forEach((el) => {
el.addEventListener('click', async () => {
if (!navigator.serial) return alert('Please enable the Web Serial API first: https://web.dev/serial/#use')
const hexHref = el.getAttribute('hex-href')
const board = el.getAttribute('board')
const verify = el.hasAttribute('verify')
const progressEl = el.querySelector('.upload-progress')
const onProgress = (progress: number) => {
progressEl.innerHTML = `${progress}%`
}
var Status = '';
let portFilters = {} as PortFilters
try {
portFilters = { filters: JSON.parse(el.getAttribute('port-filters')) || [] }
} catch (e) { }
try {
await upload(boards['nano'], hexHref, onProgress, verify, portFilters)
} catch (e) {
Status = 'Error: can\'t connect to the module. Please see troubleshooting section.<br /><code>' + e + '</code>'
//alert(e)
//throw e
}
if (Status.includes('Error: ')) {
Status = ''
try {
await upload(boards['nanoOldBootloader'], hexHref, onProgress, verify, portFilters)
} catch (e) {
Status = 'Error: can\'t connect to the module. Please see troubleshooting section.<br /><code>Old Bootloader: ' + e + '</code>'
//alert(e)
//throw e //Error: Sending 3020: receiveData timeout after 400ms
}
}
if (Status == '') {
Status = 'Done!'
}
progressEl.innerHTML = Status
console.log("Upload successful!\nEnvious? here's how https://github.com/dbuezas/arduino-web-uploader")
})
})
})

View File

@ -1,30 +1,31 @@
import { upload, boards } from './' import { upload, boards } from '.'
import { PortFilters } from './Serial' import { PortFilters } from './Serial'
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('[arduino-uploader]').forEach((el) => { document.querySelectorAll('[arduino-uploader]').forEach((el) => {
el.addEventListener('click', async () => { el.addEventListener('click', async () => {
if (!navigator.serial) return alert('Please enable the Web Serial API first: https://web.dev/serial/#use') if (!navigator.serial) return alert('Please enable the Web Serial API first: https://web.dev/serial/#use')
const hexHref = el.getAttribute('hex-href') const hexHref = el.getAttribute('hex-href')
const board = el.getAttribute('board')
const verify = el.hasAttribute('verify') const verify = el.hasAttribute('verify')
const progressEl = el.querySelector('.upload-progress') const progressEl = el.querySelector('.upload-progress')
const onProgress = (progress: number) => { const onProgress = (progress: number) => {
progressEl.innerHTML = `${progress}%` progressEl.innerHTML = `${progress}%`
} }
let portFilters = {} as PortFilters let portFilters = {} as PortFilters
try { try {
portFilters = { filters: JSON.parse(el.getAttribute('port-filters')) || [] } portFilters = { filters: JSON.parse(el.getAttribute('port-filters')) || [] }
} catch (e) { } } catch (e) { }
try { try {
await upload(boards[board], hexHref, onProgress, verify, portFilters) await upload(hexHref, onProgress, verify, portFilters)
progressEl.innerHTML = 'Done!'
} catch (e) { } catch (e) {
progressEl.innerHTML = 'Error!' progressEl.innerHTML = 'Error: can\'t connect to the module. Please see troubleshooting section.<br/><code>' + e + '</code>'
alert(e)
throw e
} }
progressEl.innerHTML = 'Done!'
console.log("Upload successful!\nEnvious? here's how https://github.com/dbuezas/arduino-web-uploader")
}) })
}) })
}) })

View File

@ -1,51 +0,0 @@
:100200000C9450010C9462010C9462010C946201F4
:100210000C9462010C9462010C9462010C946201D2
:100220000C9462010C9462010C9462010C946201C2
:100230000C9462010C9462010C9462010C946201B2
:100240000C9462010C9462010C9462010C946201A2
:100250000C9462010C9462010C9462010C94620192
:100260000C9462010C94D4010C9462010C94620110
:100270000C9462010C9462010C9462010C94620172
:100280000C9462010C9462010C9462010C94620162
:100290000C9462010C9462010C9462010C94620152
:1002A00011241FBECFEFCDBFDFE3DEBF28E2A0E009
:1002B000B8E201C01D92A430B207E1F70E94FC0130
:1002C0000C9482020C9400019091000497FF0AC0E4
:1002D000811104C080E880930604089580E880932B
:1002E000050408959FB7F894811107C08091170401
:1002F000877F809317049FBF0895809117048860BB
:10030000F8CF8FB7F89420910028309101284091C0
:10031000022850910328E091AA0AF091AB0A90912B
:10032000A60A90FF08C02F5F3F4F4F4F5F4FE091ED
:10033000AA0AF091AB0A8FBFA8EEB3E00E94680250
:1003400024E0F695E7952A95E1F76E0F7F1F811D52
:10035000911D0895CF92DF92EF92FF920E9481014A
:100360006B017C0120E4C20E22E4D21E2FE0E21ECB
:10037000F11C80E4C81682E4D8068FE0E806F10498
:1003800038F40E948101C616D706E806F906C8F3BC
:100390000E9481016C157D058E059F05C8F3FF90B5
:1003A000EF90DF90CF9008951F920F920FB60F92AB
:1003B00011248F939F93AF93BF93809100289091C6
:1003C0000128A0910228B09103280196A11DB11D1A
:1003D0008093002890930128A0930228B0930328CB
:1003E00081E08093A60ABF91AF919F918F910F906A
:1003F0000FBE0F901F90189588ED90E084BF9093EA
:10040000610080910206866080930206809100065A
:10041000816080930006809102068F7C80658093C6
:1004200002061092E20582E08093E40581E0809369
:10043000030A8EEF8093270A8093260A80E8809330
:100440002D0A80932B0A8093290A80932C0A80938B
:100450002A0A8093280A8BE08093000A87E0809321
:10046000E505E0E8FAE037E02EEF90E885E031833B
:100470002487958780837096E03C4AE0F407B9F7BB
:100480001092A10A2FE73EE32093AC0A3093AD0A05
:100490008091A50A81608093A50A1092A00A80919C
:1004A000A00A81608093A00A789490930104C0E030
:1004B000D0E081E00E9464010E94AA0180E00E94D5
:1004C00064010E94AA012097A1F30E940000F1CFCD
:1004D0000E947302A59F900DB49F900DA49F800D64
:1004E000911D11240895A29FB001B39FC001A39F45
:1004F000700D811D1124911DB29F700D811D11245D
:08050000911D0895F894FFCF4E
:0400000300000200F7
:00000001FF

View File

@ -1,62 +0,0 @@
:100000000C946F000C9481000C9481000C9481007E
:100010000C9481000C9481000C9481000C9481005C
:100020000C9481000C9481000C9481000C9481004C
:100030000C9481000C9481000C9481000C9481003C
:100040000C9438010C9481000C9481000C94810074
:100050000C9481000C9481000C9481000C9481001C
:100060000C9481000C9481000C9400000C9400000E
:100070000C9400000C9400000000000024002700F5
:100080002A002D0000000000250028002B002E0073
:10009000040404040404040402020202020203032E
:1000A000030303030505050505050503010204080F
:1000B00010204080010204081020010204081020D2
:1000C0000208010410204040000000085002010016
:1000D00000030407000000000000000000001124DD
:1000E0001FBECFEFD8E0DEBFCDBF21E0A0E0B1E082
:1000F00001C01D92A930B207E1F70E947E010C9465
:10010000E3010C940000E5EDF0E02491E9EBF0E070
:100110003491EDE9F0E09491992361F12223C9F043
:1001200021502B30B0F4E22FF0E0E756FF4F0C9453
:10013000DD01BF00C300A400BB00A900A900C600E8
:10014000CC00D000D600DA00209180002F772093D9
:100150008000E92FF0E0EE0FFF1FEC57FF4FA59155
:10016000B4919FB7F894EC91811128C030953E234B
:100170003C939FBF0895209180002F7DE8CF24B548
:100180002F7724BDE6CF24B52F7DFBCF2091B00083
:100190002F772093B000DDCF2091B0002F7DF9CFD5
:1001A000209190002F7720939000D3CF2091900042
:1001B0002F7DF9CF20919000277FF5CF3E2BD8CF10
:1001C0003FB7F8948091050190910601A091070135
:1001D000B091080126B5A89B05C02F3F19F00196E4
:1001E000A11DB11D3FBFBA2FA92F982F8827BC0191
:1001F000CD01620F711D811D911D660F771F881F34
:10020000991F08958F929F92AF92BF92CF92DF92E3
:10021000EF92FF920E94E0004B015C0188EEC82E35
:1002200083E0D82EE12CF12C0E94E00068197909B6
:100230008A099B09683E734081059105A8F321E076
:10024000C21AD108E108F10888EE880E83E0981EF2
:10025000A11CB11CC114D104E104F10429F7FF90E1
:10026000EF90DF90CF90BF90AF909F908F900895C8
:100270001F920F920FB60F9211242F933F938F93DB
:100280009F93AF93BF938091010190910201A09140
:100290000301B09104013091000120E4230F2D37B8
:1002A00028F023EC230F0196A11DB11D209300011E
:1002B0008093010190930201A0930301B093040184
:1002C0008091050190910601A0910701B09108016C
:1002D0000196A11DB11D8093050190930601A09385
:1002E0000701B0930801BF91AF919F918F913F910A
:1002F0002F910F900FBE0F901F9018958091F200D4
:1003000080618EBB90E89093F2008EB38093F200F0
:100310009093610081E0809361009093610010925E
:100320006100789494B5926094BD94B5916094BD49
:1003300023E025BD80936E0092E09093810080932E
:1003400080009091B10094609093B1008093B000D0
:10035000209391008093900084E880937A0010921B
:10036000C100E9EBF0E02491EDE9F0E0849188230D
:1003700099F090E0880F991FFC01E858FF4FA59174
:10038000B491FC01EC57FF4F859194918FB7F8948D
:10039000EC91E22BEC938FBFC0E0D0E081E00E94B3
:1003A00083000E94020180E00E9483000E940201FB
:1003B0002097A1F30E940000F1CFEE0FFF1F0590E0
:0A03C000F491E02D0994F894FFCFAA
:00000001FF

View File

@ -1,59 +0,0 @@
:100000000C945C000C946E000C946E000C946E00CA
:100010000C946E000C946E000C946E000C946E00A8
:100020000C946E000C946E000C946E000C946E0098
:100030000C946E000C946E000C946E000C946E0088
:100040000C9413010C946E000C946E000C946E00D2
:100050000C946E000C946E000C946E000C946E0068
:100060000C946E000C946E00000000002400270029
:100070002A0000000000250028002B0004040404CE
:100080000404040402020202020203030303030342
:10009000010204081020408001020408102001021F
:1000A00004081020000000080002010000030407FB
:1000B000000000000000000011241FBECFEFD8E0B8
:1000C000DEBFCDBF21E0A0E0B1E001C01D92A930AC
:1000D000B207E1F70E945D010C94CC010C94000082
:1000E000E1EBF0E02491EDE9F0E09491E9E8F0E053
:1000F000E491EE23C9F0222339F0233001F1A8F472
:10010000213019F1223029F1F0E0EE0FFF1FEE58F7
:10011000FF4FA591B4912FB7F894EC91811126C0AF
:1001200090959E239C932FBF08952730A9F02830E7
:10013000C9F0243049F7209180002F7D03C0209121
:1001400080002F7720938000DFCF24B52F7724BD48
:10015000DBCF24B52F7DFBCF2091B0002F772093EC
:10016000B000D2CF2091B0002F7DF9CF9E2BDACFF7
:100170003FB7F8948091050190910601A091070185
:10018000B091080126B5A89B05C02F3F19F0019634
:10019000A11DB11D3FBFBA2FA92F982F8827BC01E1
:1001A000CD01620F711D811D911D42E0660F771F09
:1001B000881F991F4A95D1F708958F929F92AF9209
:1001C000BF92CF92DF92EF92FF920E94B8004B0154
:1001D0005C0188EEC82E83E0D82EE12CF12C0E9421
:1001E000B800681979098A099B09683E734081053E
:1001F0009105A8F321E0C21AD108E108F10888EEC0
:10020000880E83E0981EA11CB11CC114D104E10426
:10021000F10429F7FF90EF90DF90CF90BF90AF905F
:100220009F908F9008951F920F920FB60F921124F6
:100230002F933F938F939F93AF93BF93809101012F
:1002400090910201A0910301B0910401309100014D
:1002500023E0230F2D3758F50196A11DB11D2093E2
:1002600000018093010190930201A0930301B093D8
:1002700004018091050190910601A0910701B091C0
:1002800008010196A11DB11D8093050190930601FF
:10029000A0930701B0930801BF91AF919F918F91F7
:1002A0003F912F910F900FBE0F901F90189526E849
:1002B000230F0296A11DB11DD2CF789484B5826020
:1002C00084BD84B5816084BD85B5826085BD85B5FA
:1002D000816085BD80916E00816080936E00109278
:1002E00081008091810082608093810080918100F3
:1002F0008160809381008091800081608093800084
:100300008091B10084608093B1008091B0008160E1
:100310008093B00080917A00846080937A0080910D
:100320007A00826080937A0080917A008160809365
:100330007A0080917A00806880937A001092C100E0
:10034000EDE9F0E02491E9E8F0E08491882399F068
:1003500090E0880F991FFC01E859FF4FA591B491D7
:10036000FC01EE58FF4F859194918FB7F894EC9172
:10037000E22BEC938FBFC0E0D0E081E00E947000E0
:100380000E94DD0080E00E9470000E94DD00209746
:0C039000A1F30E940000F1CFF894FFCF11
:00000001FF

File diff suppressed because one or more lines are too long

View File

@ -2,23 +2,10 @@ const webpack = require('webpack')
const path = require('path') const path = require('path')
module.exports = [{ module.exports = [{
entry: './dist/test.js', entry: './dist/nanoUploader.js',
output: { output: {
filename: 'main.js', filename: 'nano-uploader.js',
path: path.resolve(__dirname, 'test'), path: path.resolve(__dirname, 'nano-uploader'),
},
resolve: { alias: { stream: 'stream-browserify', buffer: 'buffer', process: 'process/browser' } },
plugins: [
new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'],
process: 'process',
}),
],
}, {
entry: './dist/nano.js',
output: {
filename: 'nano.js',
path: path.resolve(__dirname, 'nano'),
}, },
resolve: { alias: { stream: 'stream-browserify', buffer: 'buffer', process: 'process/browser' } }, resolve: { alias: { stream: 'stream-browserify', buffer: 'buffer', process: 'process/browser' } },
plugins: [ plugins: [