qs
Một thư viện phân tích và chuyển đổi chuỗi querystring với một số tính năng bảo mật bổ sung.
Người duy trì chính: Jordan Harband
Mô-đun qs được tạo ra và duy trì ban đầu bởi TJ Holowaychuk.
Sử Dụng
var qs = require('qs');
var assert = require('assert');
var obj = qs.parse('a=c');
assert.deepEqual(obj, { a: 'c' });
var str = qs.stringify(obj);
assert.equal(str, 'a=c');
Phân Tích Đối Tượng
qs.parse(string, [options]);
qs cho phép bạn tạo các đối tượng lồng trong chuỗi query bằng cách bao quanh tên của các khóa con bằng dấu ngoặc vuông []
. Ví dụ, chuỗi 'foo[bar]=baz'
được chuyển thành:
assert.deepEqual(qs.parse('foo[bar]=baz'), {
foo: {
bar: 'baz'
}
});
Khi sử dụng tùy chọn plainObjects
, giá trị được phân tích sẽ được trả về dưới dạng một đối tượng null, được tạo thông qua Object.create(null)
, và do đó bạn nên nhớ rằng các phương thức prototype sẽ không tồn tại trên nó và người dùng có thể đặt các tên này thành bất kỳ giá trị nào họ muốn:
var nullObject = qs.parse('a[hasOwnProperty]=b', { plainObjects: true });
assert.deepEqual(nullObject, { a: { hasOwnProperty: 'b' } });
Theo mặc định, các tham số có thể ghi đè lên các thuộc tính trên nguyên mẫu đối tượng sẽ bị bỏ qua. Nếu bạn muốn giữ dữ liệu từ các trường đó, bạn có thể sử dụng plainObjects
như đã đề cập ở trên, hoặc đặt allowPrototypes
thành true
để cho phép đầu vào của người dùng ghi đè lên các thuộc tính đó. CẢNH BÁO Thường thì không nên bật tùy chọn này vì nó có thể gây ra vấn đề khi cố gắng sử dụng các thuộc tính đã bị ghi đè. Hãy luôn cẩn thận khi sử dụng tùy chọn này.
var protoObject = qs.parse('a[hasOwnProperty]=b', { allowPrototypes: true });
assert.deepEqual(protoObject, { a: { hasOwnProperty: 'b' } });
Chuỗi được mã hóa URI cũng hoạt động:
assert.deepEqual(qs.parse('a%5Bb%5D=c'), {
a: { b: 'c' }
});
Bạn cũng có thể lồng ghép các đối tượng của bạn, như 'foo[bar][baz]=foobarbaz'
:
assert.deepEqual(qs.parse('foo[bar][baz]=foobarbaz'), {
foo: {
bar: {
baz: 'foobarbaz'
}
}
});
Theo mặc định, khi lồng ghép các đối tượng qs sẽ chỉ phân tích đến 5 cấp con. Điều này có nghĩa nếu bạn cố gắng phân tích một chuỗi như 'a[b][c][d][e][f][g][h][i]=j'
thì đối tượng kết quả của bạn sẽ là:
var expected = {
a: {
b: {
c: {
d: {
e: {
f: {
'[g][h][i]': 'j'
}
}
}
}
}
}
};
var string = 'a[b][c][d][e][f][g][h][i]=j';
assert.deepEqual(qs.parse(string), expected);
Sâu này có thể được ghi đè bằng cách truyền một tùy chọn depth
vào qs.parse(string, [options])
:
var deep = qs.parse('a[b][c][d][e][f][g][h][i]=j', { depth: 1 });
assert.deepEqual(deep, { a: { b: { '[c][d][e][f][g][h][i]': 'j' } } });
Giới hạn độ sâu giúp giảm thiểu việc lạm dụng khi qs được sử dụng để phân tích đầu vào của người dùng, và được khuyến nghị giữ nó ở một số lượng nhỏ hợp lý.
Vì cùng những lý do tương tự, theo mặc định qs chỉ phân tích tối đa 1000 tham số. Điều này có thể bị ghi đè bằng cách truyền tùy chọn parameterLimit
:
var limited = qs.parse('a=b&c=d', { parameterLimit: 1 });
assert.deepEqual(limited, { a: 'b' });
Để bỏ qua dấu hỏi ban đầu, sử dụng ignoreQueryPrefix
:
var prefixed = qs.parse('?a=b&c=d', { ignoreQueryPrefix: true });
assert.deepEqual(prefixed, { a: 'b', c: 'd' });
Một dấu phân cách tùy chọn cũng có thể được truyền vào:
var delimited = qs.parse('a=b;c=d', { delimiter: ';' });
assert.deepEqual(delimited, { a: 'b', c: 'd' });
Dấu phân cách có thể là một biểu thức chính quy:
var regexed = qs.parse('a=b;c=d,e=f', { delimiter: /[;,]/ });
assert.deepEqual(regexed, { a: 'b', c: 'd', e: 'f' });
Tùy chọn allowDots
có thể được sử dụng để bật chế độ ghi chú chấm:
var withDots = qs.parse('a.b=c', { allowDots: true });
assert.deepEqual(withDots, { a: { b: 'c' } });
Nếu bạn phải làm việc với các trình duyệt hoặc dịch vụ cổ điển, cũng có hỗ trợ để giải mã các octet được mã hóa dưới dạng iso-8859-1:
var oldCharset = qs.parse('a=%A7', { charset: 'iso-8859-1' });
assert.deepEqual(oldCharset, { a: '§' });
Một số dịch vụ thêm giá trị utf8=✓
ban đầu vào các biểu mẫu để phiên bản Internet Explorer cũ hơn có khả năng gửi biểu mẫu dưới dạng utf-8 hơn. Ngoài ra, máy chủ có thể kiểm tra giá trị này so với các mã hóa sai của ký tự đánh dấu và phát hiện rằng chuỗi truy vấn hoặc thân yêu cầu application/x-www-form-urlencoded
không được gửi dưới dạng utf-8, ví dụ nếu biểu mẫu có tham số accept-charset
hoặc trang chứa có một bảng mã khác.
qs hỗ trợ cơ chế này thông qua tùy chọn charsetSentinel
. Nếu được chỉ định, tham số utf8
sẽ không được bao gồm trong đối tượng trả về. Nó sẽ được sử dụng để chuyển sang chế độ iso-8859-1
/utf-8
dựa vào cách ký tự đánh dấu được mã hóa.
Quan trọng : Khi bạn chỉ định cả tùy chọn charset
và tùy chọn charsetSentinel
, charset
sẽ bị ghi đè khi yêu cầu chứa tham số utf8
từ đó bảng mã thực tế có thể được suy ra. Theo cách này, charset
sẽ hoạt động như bảng mã mặc định thay vì bảng mã chính thống.
var detectedAsUtf8 = qs.parse('utf8=%E2%9C%93&a=%C3%B8', {
charset: 'iso-8859-1',
charsetSentinel: true
});
assert.deepEqual(detectedAsUtf8, { a: 'ø' });
// Browsers encode the checkmark as ✓ when submitting as iso-8859-1:
var detectedAsIso8859_1 = qs.parse('utf8=%26%2310003%3B&a=%F8', {
charset: 'utf-8',
charsetSentinel: true
});
assert.deepEqual(detectedAsIso8859_1, { a: 'ø' });
Nếu bạn muốn giải mã cú pháp &#...;
thành ký tự thực tế, bạn có thể chỉ định tùy chọn interpretNumericEntities
như sau:
var detectedAsIso8859_1 = qs.parse('a=%26%239786%3B', {
charset: 'iso-8859-1',
interpretNumericEntities: true
});
assert.deepEqual(detectedAsIso8859_1, { a: '☺' });
Nó cũng hoạt động khi bảng mã đã được phát hiện ở chế độ charsetSentinel
.
Phân Tích Mảng
qs cũng có thể phân tích mảng bằng cách sử dụng cú pháp []
tương tự:
var withArray = qs.parse('a[]=b&a[]=c');
assert.deepEqual(withArray, { a: ['b', 'c'] });
Bạn cũng có thể chỉ định một chỉ mục:
var withIndexes = qs.parse('a[1]=c&a[0]=b');
assert.deepEqual(withIndexes, { a: ['b', 'c'] });
Lưu ý rằng sự khác biệt duy nhất giữa một chỉ mục trong một mảng và một khóa trong một đối tượng là giá trị giữa dấu ngoặc vuông phải là một số để tạo một mảng. Khi tạo mảng với các chỉ mục cụ thể, qs sẽ nén một mảng thưa thành chỉ các giá trị hiện có và bảo toàn thứ tự của chúng:
var noSparse = qs.parse('a[1]=b&a[15]=c');
assert.deepEqual(noSparse, { a: ['b', 'c'] });
Bạn cũng có thể sử dụng tùy chọn allowSparse
để phân tích các mảng thưa:
var sparseArray = qs.parse('a[1]=2&a[3]=5', { allowSparse: true });
assert.deepEqual(sparseArray, { a: [, '2', , '5'] });
Lưu ý rằng một chuỗi trống cũng là một giá trị và sẽ được bảo toàn:
var withEmptyString = qs.parse('a[]=&a[]=b');
assert.deepEqual(withEmptyString, { a: ['', 'b'] });
var withIndexedEmptyString = qs.parse('a[0]=b&a[1]=&a[2]=c');
assert.deepEqual(withIndexedEmptyString, { a: ['b', '', 'c'] });
qs cũng sẽ giới hạn việc chỉ định chỉ mục trong một mảng với chỉ mục tối đa là 20
. Bất kỳ thành viên mảng nào có chỉ mục lớn hơn 20
sẽ thay thế bằng một đối tượng với chỉ mục làm khóa. Điều này cần thiết để xử lý trường hợp khi ai đó gửi, ví dụ, a[999999999]
và nó sẽ mất thời gian đáng kể để lặp qua mảng lớn này.
var withMaxIndex = qs.parse('a[100]=b');
assert.deepEqual(withMaxIndex, { a: { '100': 'b' } });
Giới hạn này có thể được ghi đè bằng cách truyền tùy chọn arrayLimit
:
var withArrayLimit = qs.parse('a[1]=b', { arrayLimit: 0 });
assert.deepEqual(withArrayLimit, { a: { '1': 'b' } });
Để tắt hoàn toàn việc phân tích mảng, đặt parseArrays
thành false
.
var noParsingArrays = qs.parse('a[]=b', { parseArrays: false });
assert.deepEqual(noParsingArrays, { a: { '0': 'b' } });
Nếu bạn kết hợp các cú pháp, qs sẽ hợp nhất hai mục thành một đối tượng:
var mixedNotation = qs.parse('a[0]=b&a[b]=c');
assert.deepEqual(mixedNotation, { a: { '0': 'b', b: 'c' } });
Bạn cũng có thể tạo các mảng của các đối tượng:
var arraysOfObjects = qs.parse('a[][b]=c');
assert.deepEqual(arraysOfObjects, { a: [{ b: 'c' }] });
Một số người sử dụng dấu phẩy để kết hợp các mảng, qs có thể phân tích nó:
var arraysOfObjects = qs.parse('a=b,c', { comma: true })
assert.deepEqual(arraysOfObjects, { a: ['b', 'c'] })
( this cannot convert nested objects, such as_a={b:1},{c:d}_
)
Phân Tích Giá Trị Nguyên Thủy/Scaler (số, boolean, null, v.v.)
Theo mặc định, tất cả giá trị được phân tích dưới dạng chuỗi. Hành vi này sẽ không thay đổi và được giải thích trong issue #91.
var primitiveValues = qs.parse('a=15&b=true&c=null');
assert.deepEqual(primitiveValues, { a: '15', b: 'true', c: 'null' });
Nếu bạn muốn tự động chuyển đổi các giá trị giống như số, boolean và các giá trị khác thành các giá trị nguyên thủy tương ứng, bạn có thể sử dụng query-types Express JS middleware để tự động chuyển đổi tất cả các tham số truy vấn yêu cầu.
Chuyển Đổi Thành Chuỗi
qs.stringify(object, [options]);
Khi chuyển đổi thành chuỗi, qs mặc định mã hóa URI đầu ra. Các đối tượng được chuyển thành chuỗi theo như bạn mong đợi:
assert.equal(qs.stringify({ a: 'b' }), 'a=b');
assert.equal(qs.stringify({ a: { b: 'c' } }), 'a%5Bb%5D=c');
Mã hóa này có thể bị tắt bằng cách đặt tùy chọn encode
thành false
:
var unencoded = qs.stringify({ a: { b: 'c' } }, { encode: false });
assert.equal(unencoded, 'a[b]=c');
Mã hóa có thể bị tắt cho các khóa bằng cách đặt tùy chọn encodeValuesOnly
thành true
:
var encodedValues = qs.stringify(
{ a: 'b', c: ['d', 'e=f'], f: [['g'], ['h']] },
{ encodeValuesOnly: true }
);
assert.equal(encodedValues,'a=b&c[0]=d&c[1]=e%3Df&f[0][0]=g&f[1][0]=h');
Mã hóa này cũng có thể được thay thế bằng một phương pháp mã hóa tùy chỉnh được đặt là tùy chọn encoder
:
var encoded = qs.stringify({ a: { b: 'c' } }, { encoder: function (str) {
// Passed in values `a`, `b`, `c`
return // Return encoded string
}})
(Lưu ý: tùy chọn_encoder_
không áp dụng nếu_encode_
là_false_
)
Tương tự với tùy chọn encoder
, có một tùy chọn decoder
cho parse
để ghi đè quá trình giải mã các thuộc tính và giá trị:
var decoded = qs.parse('x=z', { decoder: function (str) {
// Passed in values `x`, `z`
return // Return decoded string
}})
Bạn có thể mã hóa các khóa và giá trị bằng logic khác nhau bằng cách sử dụng đối số kiểu được cung cấp cho bộ mã hóa:
var encoded = qs.stringify({ a: { b: 'c' } }, { encoder: function (str, defaultEncoder, charset, type) {
if (type === 'key') {
return // Encoded key
} else if (type === 'value') {
return // Encoded value
}
}})
Đối số kiểu cũng được cung cấp cho bộ giải mã:
var decoded = qs.parse('x=z', { decoder: function (str, defaultDecoder, charset, type) {
if (type === 'key') {
return // Decoded key
} else if (type === 'value') {
return // Decoded value
}
}})
Các ví dụ sau đây sẽ được hiển thị nhưng đầu ra không được mã hóa URI để dễ hiểu. Vui lòng lưu ý rằng giá trị trả về trong các trường hợp này sẽ được mã hóa URI trong thực tế khi sử dụng.
Khi chuỗi mảng, theo mặc định chúng được cung cấp chỉ mục rõ ràng:
qs.stringify({ a: ['b', 'c', 'd'] });
// 'a[0]=b&a[1]=c&a[2]=d'
Bạn có thể ghi đè bằng cách đặt tùy chọn indices
thành false
:
qs.stringify({ a: ['b', 'c', 'd'] }, { indices: false });
// 'a=b&a=c&a=d'
Bạn có thể sử dụng tùy chọn arrayFormat
để chỉ định định dạng của mảng đầu ra:
qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'indices' })
// 'a[0]=b&a[1]=c'
qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'brackets' })
// 'a[]=b&a[]=c'
qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'repeat' })
// 'a=b&a=c'
qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'comma' })
// 'a=b,c'
Lưu ý: khi sử dụng arrayFormat
được đặt thành 'comma'
, bạn cũng có thể truyền tùy chọn commaRoundTrip
được đặt thành true
hoặc false
, để thêm []
vào các mảng có một mục, để họ có thể qua lại qua quá trình phân tích.
Khi các đối tượng được chuyển thành chuỗi, theo mặc định chúng sử dụng ký hiệu ngoặc vuông:
qs.stringify({ a: { b: { c: 'd', e: 'f' } } });
// 'a[b][c]=d&a[b][e]=f'
Bạn có thể ghi đè để sử dụng ký hiệu dấu chấm bằng cách đặt tùy chọn allowDots
thành true
:
qs.stringify({ a: { b: { c: 'd', e: 'f' } } }, { allowDots: true });
// 'a.b.c=d&a.b.e=f'
Chuỗi trống và giá trị null sẽ bỏ qua giá trị, nhưng dấu bằng (=) vẫn nằm ở đó:
assert.equal(qs.stringify({ a: '' }), 'a=');
Khóa không có giá trị (như một đối tượng trống hoặc mảng trống) sẽ không trả về gì cả:
assert.equal(qs.stringify({ a: [] }), '');
assert.equal(qs.stringify({ a: {} }), '');
assert.equal(qs.stringify({ a: [{}] }), '');
assert.equal(qs.stringify({ a: { b: []} }), '');
assert.equal(qs.stringify({ a: { b: {}} }), '');
Các thuộc tính được đặt thành undefined
sẽ bị bỏ qua hoàn toàn:
assert.equal(qs.stringify({ a: null, b: undefined }), 'a=');
Chuỗi truy vấn có thể tùy ý được đặt trước bằng dấu hỏi:
assert.equal(qs.stringify({ a: 'b', c: 'd' }, { addQueryPrefix: true }), '?a=b&c=d');
Delimiter có thể được ghi đè bằng cách sử dụng stringify:
assert.equal(qs.stringify({ a: 'b', c: 'd' }, { delimiter: ';' }), 'a=b;c=d');
Nếu bạn chỉ muốn ghi đè việc biểu diễn của các đối tượng Date
, bạn có thể cung cấp tùy chọn serializeDate
:
var date = new Date(7);
assert.equal(qs.stringify({ a: date }), 'a=1970-01-01T00:00:00.007Z'.replace(/:/g, '%3A'));
assert.equal(
qs.stringify({ a: date }, { serializeDate: function (d) { return d.getTime(); } }),
'a=7'
);
Bạn có thể sử dụng tùy chọn sort
để ảnh hưởng đến thứ tự các khóa tham số:
function alphabeticalSort(a, b) {
return a.localeCompare(b);
}
assert.equal(qs.stringify({ a: 'c', z: 'y', b : 'f' }, { sort: alphabeticalSort }), 'a=c&b=f&z=y');
Cuối cùng, bạn có thể sử dụng tùy chọn filter
để hạn chế những khóa sẽ được bao gồm trong chuỗi đầu ra. Nếu bạn truyền một hàm, nó sẽ được gọi cho mỗi khóa để có được giá trị thay thế. Nếu không, nếu bạn truyền một mảng, nó sẽ được sử dụng để chọn các thuộc tính và chỉ số mảng để biểu diễn dưới dạng chuỗi:
function filterFunc(prefix, value) {
if (prefix == 'b') {
// Return an `undefined` value to omit a property.
return;
}
if (prefix == 'e[f]') {
return value.getTime();
}
if (prefix == 'e[g][0]') {
return value * 2;
}
return value;
}
qs.stringify({ a: 'b', c: 'd', e: { f: new Date(123), g: [2] } }, { filter: filterFunc });
// 'a=b&c=d&e[f]=123&e[g][0]=4'
qs.stringify({ a: 'b', c: 'd', e: 'f' }, { filter: ['a', 'e'] });
// 'a=b&e=f'
qs.stringify({ a: ['b', 'c', 'd'], e: 'f' }, { filter: ['a', 0, 2] });
// 'a[0]=b&a[2]=d'
Bạn cũng có thể sử dụng filter
để chèn việc biểu diễn tùy chỉnh cho các loại do người dùng xác định. Hãy xem xét bạn đang làm việc với một API mong đợi chuỗi truy vấn với định dạng dành cho phạm vi:
https://domain.com/endpoint?range=30...70
Mà bạn mô hình như sau:
class Range {
constructor(from, to) {
this.from = from;
this.to = to;
}
}
Bạn có thể chèn một trình biểu diễn tùy chỉnh để xử lý các giá trị của loại này:
qs.stringify(
{
range: new Range(30, 70),
},
{
filter: (prefix, value) => {
if (value instanceof Range) {
return `${value.from}...${value.to}`;
}
// serialize the usual way
return value;
},
}
);
// range=30...70
Xử lý các giá trị null
Theo mặc định, các giá trị null
được xem xét giống như chuỗi trống:
var withNull = qs.stringify({ a: null, b: '' });
assert.equal(withNull, 'a=&b=');
Phân tích không phân biệt giữa các tham số có dấu bằng và không có dấu bằng. Cả hai đều được chuyển thành chuỗi trống.
var equalsInsensitive = qs.parse('a&b=');
assert.deepEqual(equalsInsensitive, { a: '', b: '' });
Để phân biệt giữa các giá trị null
và chuỗi trống, hãy sử dụng cờ strictNullHandling
. Trong chuỗi kết quả, các giá trị null
không có dấu =
:
var strictNull = qs.stringify({ a: null, b: '' }, { strictNullHandling: true });
assert.equal(strictNull, 'a&b=');
Để phân tích các giá trị không có =
trở lại null
, hãy sử dụng cờ strictNullHandling
:
var parsedStrictNull = qs.parse('a&b=', { strictNullHandling: true });
assert.deepEqual(parsedStrictNull, { a: null, b: '' });
Để hoàn toàn bỏ qua việc hiển thị các khóa có giá trị null
, hãy sử dụng cờ skipNulls
:
var nullsSkipped = qs.stringify({ a: 'b', c: null}, { skipNulls: true });
assert.equal(nullsSkipped, 'a=b');
Nếu bạn đang giao tiếp với các hệ thống kế thừa, bạn có thể chuyển sang iso-8859-1
bằng cách sử dụng tùy chọn charset
:
var iso = qs.stringify({ æ: 'æ' }, { charset: 'iso-8859-1' });
assert.equal(iso, '%E6=%E6');
Các ký tự không tồn tại trong iso-8859-1
sẽ được chuyển thành các thực thể số, tương tự như cách các trình duyệt hoạt động:
var numeric = qs.stringify({ a: '☺' }, { charset: 'iso-8859-1' });
assert.equal(numeric, 'a=%26%239786%3B');
Bạn có thể sử dụng tùy chọn charsetSentinel
để thông báo ký tự bằng cách bao gồm một tham số utf8=✓
với mã hóa chính xác nếu biểu tượng đánh dấu, tương tự như cách Ruby on Rails và các hệ thống khác thực hiện khi gửi biểu mẫu.
var sentinel = qs.stringify({ a: '☺' }, { charsetSentinel: true });
assert.equal(sentinel, 'utf8=%E2%9C%93&a=%E2%98%BA');
var isoSentinel = qs.stringify({ a: 'æ' }, { charsetSentinel: true, charset: 'iso-8859-1' });
assert.equal(isoSentinel, 'utf8=%26%2310003%3B&a=%E6');
Xử lý các bộ ký tự đặc biệt
Theo mặc định, việc mã hóa và giải mã các ký tự được thực hiện trong utf-8
, và hỗ trợ iso-8859-1
cũng được tích hợp thông qua tham số charset
.
Nếu bạn muốn mã hóa chuỗi truy vấn thành bộ ký tự khác (ví dụ: Shift JIS), bạn có thể sử dụng thư viện qs-iconv:
var encoder = require('qs-iconv/encoder')('shift_jis');
var shiftJISEncoded = qs.stringify({ a: 'こんにちは!' }, { encoder: encoder });
assert.equal(shiftJISEncoded, 'a=%82%B1%82%F1%82%C9%82%BF%82%CD%81I');
Điều này cũng hoạt động khi giải mã các chuỗi truy vấn:
var decoder = require('qs-iconv/decoder')('shift_jis');
var obj = qs.parse('a=%82%B1%82%F1%82%C9%82%BF%82%CD%81I', { decoder: decoder });
assert.deepEqual(obj, { a: 'こんにちは!' });
Mã hóa khoảng trắng theo RFC 3986 và RFC 1738
RFC3986 được sử dụng làm tùy chọn mặc định và mã hóa ‘ ‘ thành %20 để tương thích ngược. Đồng thời, đầu ra có thể được biểu diễn dưới dạng RFC1738 với ‘ ‘ tương đương ‘+’.
assert.equal(qs.stringify({ a: 'b c' }), 'a=b%20c');
assert.equal(qs.stringify({ a: 'b c' }, { format : 'RFC3986' }), 'a=b%20c');
assert.equal(qs.stringify({ a: 'b c' }, { format : 'RFC1738' }), 'a=b+c');
Bảo mật
Vui lòng gửi email tới @ljharb hoặc xem https://tidelift.com/security nếu bạn có một lỗ hổng bảo mật tiềm ẩn cần báo cáo.
qs cho doanh nghiệp
Có sẵn như một phần của Tidelift Subscription
Những người duy trì qs và hàng ngàn gói khác đang làm việc với Tidelift để cung cấp hỗ trợ thương mại và bảo trì cho các phụ thuộc mã nguồn mở bạn sử dụng để xây dựng ứng dụng của mình. Tiết kiệm thời gian, giảm rủi ro và cải thiện sức khỏe mã nguồn, đồng thời trả tiền cho người duy trì các phụ thuộc chính xác mà bạn sử dụng. Learn more.
Thông Tin Tải Xuống:
Tác giả: ljharb
Mã nguồn: https://github.com/ljharb/qs
Giấy phép: BSD-3-Clause license