Desenvolver para SharePoint Server Subscription Edition: as práticas que aguentam
A Subscription Edition tem suporte até pelo menos 2035 e o teto da SharePoint Framework não se move desde 2023. É essa combinação que define o trabalho: uma pista longa sobre uma base estável. Como construir bem em cima dela, com código que corre na versão que o seu farm aceita mesmo.
A Subscription Edition tem suporte até pelo menos 31 de dezembro de 2035, ao abrigo da Modern Lifecycle Policy, sem fim de suporte previsto. É uma pista mais longa do que a da maioria do software que se escreve este ano.
O teto da SharePoint Framework, esse, não se move desde 2023.
Estes dois factos juntos são o trabalho todo. Constrói-se uma coisa que tem de continuar a funcionar durante uma década, sobre uma versão de framework que provavelmente não vai mudar por baixo. É uma posição pouco comum, e recompensa um cuidado muito particular.
Saber o teto antes de escrever seja o que for
O SharePoint on-premises só corre as versões de SPFx que correspondem às suas dependências do lado do servidor. A tabela de compatibilidade dá à Subscription Edition v1.0 a v1.5, e a atualização de funcionalidades 23H1 acrescentou suporte para SPFx 1.5.1, descrito na altura como "um passo na nossa viagem de longo prazo para melhorar e alargar as capacidades da SharePoint Framework no SharePoint Server Subscription Edition".
A atualização seguinte, a 23H2, foi um passo mais além e acrescentou suporte a React 16 e Office UI Fabric React 7 nas soluções de SPFx. Depois disso, nada: lendo da 24H1 à 26H1, nenhuma menciona sequer SPFx. A 26H1 traz o Modern People Card, o Document Intelligence e uma opção de configuração do People Picker. Nada sobre a framework.
Também vai encontrar artigos da comunidade a reclamar um teto bastante mais alto. Não conseguimos confirmar nenhum deles contra uma fonte de primeira mão, por isso não vamos repetir um número de versão que não podemos sustentar.
Daqui sai uma instrução prática, e não um número: verifique no farm, não no artigo. Publique um pacote trivial, construído na versão que tenciona usar, no seu próprio catálogo de aplicações, e veja se carrega. Quinze minutos disso valem mais do que qualquer tabela, incluindo a de cima, porque o teto é uma propriedade do seu build.
Seja qual for o resultado, conte que fique quieto. Desenhe como se a versão que tem fosse a versão que fica.
O ambiente que constrói mesmo
A primeira armadilha é a cadeia de ferramentas, e apanha qualquer pessoa uma vez.
O gerador Yeoman atual não o vai ajudar. A partir da versão 1.13.0 só produz projetos para SharePoint Online. Instale o mais recente e o alvo on-premises pura e simplesmente não aparece. É preciso um gerador suficientemente antigo para ainda perguntar:
npm install gulp-cli@2.3.0 --global
npm install yo@2.0.6 --global
npm install @microsoft/generator-sharepoint@1.10.0 --global
Depois escolhe-se o alvo on-premises no fluxo do gerador.
O Node é antigo. O SPFx 1.4.1 e o 1.5.1 apontam por omissão ao Node 6 e 8. A documentação nota que se conseguem pôr a funcionar em Node v12.18.1, v14.17.1 e v16.15.0 com duas alterações: graceful-fs na v4 ou posterior, e substituir o node-sass por sass. É a diferença entre uma máquina de build que alguém ainda consegue levantar em 2026 e outra que não.
Registe tudo isso no repositório. A versão do gerador, a do Node, a do gulp-cli. Daqui a três anos quem pegar nisto não vai adivinhar, e não haverá artigo nenhum para lhe dizer. Um .nvmrc e uma secção curta de CONTRIBUTING não custam nada agora e poupam uma semana depois.
Planeie o caso desligado. Gerar o projeto precisa de acesso ao npm. Se as máquinas de desenvolvimento não chegam ao registo, é preciso um registo local, e a orientação da própria Microsoft é frontal quanto ao custo: mais software e uma quantidade significativa de trabalho de manutenção. Orce isso com honestidade em vez de o descobrir à terceira semana.
Os comandos de build são os do gulp, que é o único sítio onde os artigos mais antigos continuam certos:
gulp serve
gulp bundle --ship
gulp package-solution --ship
Falar com o SharePoint
O SPHttpClient é o caminho. Trata do request digest e da autenticação, e comporta-se da mesma maneira em todas as versões do SPFx, o que faz dele a coisa mais duradoura da caixa.
Ponha-o num serviço. Não num componente.
import { SPHttpClient, SPHttpClientResponse } from '@microsoft/sp-http';
export interface IDocument {
Id: number;
Title: string;
Modified: string;
}
// Built apart from the call that uses it, so it can be exercised without a
// client, a farm or a browser. It is also the line most likely to be wrong.
export function itemsUrl(webUrl: string, listTitle: string, top: number): string {
// A list called "HR & Payroll" or "Q1 'draft' items" breaks this. The test
// list is always called Documents, which is why it reaches production.
var list = encodeURIComponent(listTitle.replace(/'/g, "''"));
return webUrl + "/_api/web/lists/getbytitle('" + list + "')/items" +
'?$select=Id,Title,Modified' +
'&$orderby=Modified desc' +
'&$top=' + top;
}
// One place decides what an HTTP status means. Components never see numbers.
// Not called describe, because mocha puts a global function of that name in
// every test file, and the collision confuses long before it fails.
export function describeStatus(status: number): string {
if (status === 401) { return 'notAuthenticated'; }
if (status === 403) { return 'accessDenied'; }
if (status === 404) { return 'notFound'; }
if (status === 429 || status === 503) { return 'throttled'; }
return 'unknown';
}
export class DocumentService {
constructor(
private readonly client: SPHttpClient,
private readonly webUrl: string
) {}
public getRecent(listTitle: string, top: number): Promise<IDocument[]> {
return this.client
.get(itemsUrl(this.webUrl, listTitle, top), SPHttpClient.configurations.v1)
.then(function (response: SPHttpClientResponse): Promise<any> {
if (!response.ok) {
return Promise.reject(describeStatus(response.status));
}
return response.json();
})
.then(function (json: { value: IDocument[] }): IDocument[] {
return json.value;
});
}
}
Há quatro pormenores ali dentro que valem mais do que parecem.
O URL é construído por uma função própria. Não é arrumação: é a única parte do serviço que se consegue testar sem um farm, e a secção seguinte apoia-se nela.
A plica é duplicada antes de o título ser codificado, e a ordem importa. A sintaxe REST do SharePoint mete o título da lista entre plicas, por isso uma lista chamada Q1 'draft' items fecha a cadeia mais cedo; o OData escapa uma plica duplicando-a. O encodeURIComponent deixa a plica de propósito em paz, portanto duplicar depois seria tarde de mais.
O $select é explícito. Pedir tudo e usar três campos é largura de banda que se paga em cada render, e num farm que serve alguns milhares de pessoas isso não sai de graça.
A cadeia de promises não é nostalgia. O TypeScript 2.4 a compilar para ES5 não dá async nem await sem um runtime de geradores. A cadeia é a versão que compila sempre.
Se quiser uma API mais simpática, o PnPjs funciona, mas tem de fixar uma versão que suporte o seu SPFx e tratar esse pin como permanente. Um npm update que o empurre para a frente em silêncio produz uma solução que compila na sua máquina e falha no servidor, que é a pior classe de defeito para diagnosticar.
Componentes, e que React se apanha de facto
A documentação contradiz-se aqui, e vale mais resolver isso do que contorná-lo.
Três fontes de primeira mão apontam no mesmo sentido. A tabela de compatibilidade de plataformas do SPFx dá à Subscription Edition v1.0 a v1.5. A nota de lançamento da 23H1 acrescenta suporte a SPFx 1.5.1. A nota de lançamento da 23H2 acrescenta suporte a React 16 e Office UI Fabric React 7, "permitindo aos programadores usar estas versões mais recentes dos componentes nas suas soluções de SharePoint Framework".
Uma página aponta no sentido contrário. O artigo de programação de SPFx para on-premises continua a afirmar que a Subscription Edition "tem exatamente as mesmas dependências e requisitos para a SharePoint Framework que o SharePoint Server 2019", e manda usar a v1.4.1. Essa página não acompanhou: contradiz a tabela de compatibilidade publicada no mesmo sítio, e descreve o produto como ele era antes da 23H1. Duas fontes atuais contra uma desatualizada não é empate, portanto a resposta são as notas de versão.
Daqui sai uma coisa concreta. Os hooks chegaram no React 16.8. Num farm na 23H2 ou posterior, o React 16 é suportado, portanto os hooks estão à sua disposição desde que instale o React 16.8 ou superior e o fixe. A nota de versão nomeia a versão maior e não a menor, o que é exatamente a razão para o fixar e para registar esse pin.
Abaixo da 23H2, e no SharePoint 2019 e 2016, está em React 15 e todos os componentes são classes.
O componente em classe vale a pena de qualquer maneira, porque é o que corre em todo o lado: na 1.4.1 e na 1.5.1, acima e abaixo da 23H2. Se está a escrever uma coisa que tem de sobreviver a um calendário de atualizações que não controla, é este o chão, e custa três coisas feitas à mão.
import * as React from 'react';
import { DocumentService, IDocument } from '../services/DocumentService';
export interface IRecentDocumentsProps {
service: DocumentService;
listTitle: string;
top: number;
}
export interface IRecentDocumentsState {
status: string;
items: IDocument[];
}
export default class RecentDocuments extends React.Component<
IRecentDocumentsProps,
IRecentDocumentsState
> {
private _mounted: boolean = false;
private _request: number = 0;
constructor(props: IRecentDocumentsProps) {
super(props);
this.state = { status: 'loading', items: [] };
}
public componentDidMount(): void {
this._mounted = true;
this._load();
}
public componentWillUnmount(): void {
// There is no cleanup function to return here. Without this flag, a slow
// list on a busy farm updates a component that is already gone every time
// somebody navigates away mid-load.
this._mounted = false;
}
public componentDidUpdate(previous: IRecentDocumentsProps): void {
// Compare first. Reloading unconditionally is an infinite loop: load, set
// state, re-render, load again. It is silent until somebody opens the
// network tab.
if (previous.listTitle !== this.props.listTitle || previous.top !== this.props.top) {
this._load();
}
}
private _load(): void {
var self = this;
var request = ++this._request;
this.setState({ status: 'loading', items: [] });
this.props.service
.getRecent(this.props.listTitle, this.props.top)
.then(function (items: IDocument[]): void {
// A slower answer to an older request must never overwrite a newer one.
if (!self._mounted || request !== self._request) { return; }
self.setState({ status: items.length ? 'ready' : 'empty', items: items });
})
.catch(function (failure: string): void {
if (!self._mounted || request !== self._request) { return; }
self.setState({
status: failure === 'accessDenied' ? 'accessDenied' : 'error',
items: []
});
});
}
public render(): React.ReactElement<IRecentDocumentsProps> {
if (this.state.status === 'loading') {
return <p role="status">Loading recent documents.</p>;
}
if (this.state.status === 'accessDenied') {
return (
<p role="alert">
You do not have access to this library. Ask the site owner for read access.
</p>
);
}
if (this.state.status === 'error') {
return <p role="alert">The library could not be reached. Try again in a moment.</p>;
}
if (this.state.status === 'empty') {
return <p>No documents have been added yet.</p>;
}
return (
<ul>
{this.state.items.map(function (item: IDocument): JSX.Element {
return <li key={item.Id}>{item.Title}</li>;
})}
</ul>
);
}
}
A flag _mounted substitui a função de limpeza que um hook moderno devolveria.
O contador _request substitui tudo aquilo a que se recorreria para tratar concorrência. Duas alterações seguidas no painel de propriedades lançam dois pedidos, e eles voltam pela ordem que o farm decidir. Sem o contador, a resposta mais antiga pode chegar em último e ganhar.
A comparação no componentDidUpdate é o erro clássico dos componentes em classe. Sem ela, construiu-se um ciclo infinito contra o próprio farm.
Temas
Não há serviço ThemeProvider nesta versão nem Fluent UI v9. O que há são tokens de tema em SCSS, e chegam:
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
.recentDocuments {
color: "[theme: bodyText, default: #333333]";
background-color: "[theme: white, default: #ffffff]";
padding: 12px;
}
.recentDocuments a {
color: "[theme: themePrimary, default: #0078d4]";
}
.recentDocuments a:hover {
color: "[theme: themeDarkAlt, default: #106ebe]";
}
.recentDocuments .meta {
color: "[theme: neutralSecondary, default: #666666]";
}
A regra é a mesma que se aplica em qualquer plataforma e em qualquer versão: a solução tira as cores do site. Escreva um valor hexadecimal diretamente e construiu a coisa que se parte no dia em que alguém aplicar outro tema, e num farm que serve vários departamentos com vários temas esse dia está próximo.
O valor default: é o que aparece onde não há tema aplicado. Escolha-o de maneira a que a web part continue legível em vez de invisível.
As permissões são experiência de utilização
Uma falha não é uma coisa só, e on-premises isto pesa mais do que se espera, porque as permissões num farm com estruturas herdadas e segmentação de público são genuinamente complicadas.
Cinco situações, e a pessoa pode agir sobre quatro delas:
- Sessão não iniciada. Atualizar a página e entrar outra vez.
- Acesso negado. Pedir ao proprietário do site. Não é um erro que o leitor tenha causado, e merece uma instrução em vez de um pedido de desculpa.
- Não encontrado. A lista foi renomeada ou apagada. Diga qual estava a procurar.
- Throttling ou indisponibilidade. O farm está ocupado. Ofereça uma forma de tentar outra vez.
- Não há nada. A consulta funcionou e a resposta é vazia. Isso não é falha nenhuma.
Mapeie-as uma vez, no serviço, como o código acima faz. Depois desenhe cada uma. Uma web part que diz "Ocorreu um erro" nas cinco é uma web part que gera um pedido de suporte em cada uma delas.
E o Microsoft Graph
Esta pergunta volta sempre, e a resposta habitual, que os dados estão nos seus servidores e portanto o Graph é irrelevante, é fácil de mais. Há muitos farms ao lado de um tenant Microsoft 365, e querer dados de perfil de lá é perfeitamente razoável.
A resposta concreta serve mais. O MSGraphClient e o AadHttpClient foram ambos introduzidos no SPFx 1.4.1, portanto as classes existem na sua versão. Duas coisas estão no caminho.
As notas de lançamento da 1.4.1 descrevem-nos como developer preview, "disponíveis para utilização em pré-visualização dentro do SharePoint Online", e explicitamente "não destinados a utilização em produção por agora".
Mais decisivo ainda, o modelo de permissões de que dependem é infraestrutura do SharePoint Online de ponta a ponta. Declaram-se webApiPermissionRequests no package-solution.json; publicar no catálogo de aplicações cria pedidos de permissão; um administrador aprova-os na página de acesso a APIs do centro de administração do SharePoint; a concessão fica guardada na aplicação SharePoint Online Client Extensibility que a Microsoft aprovisiona em todos os Entra ID. Um farm seu não tem nada disso, portanto não há a quem pedir nem a quem conceder.
O contorno óbvio também está fechado: a documentação afirma que usar a Microsoft Authentication Library diretamente com o SPFx não é suportado a partir da v1.4.1.
Para ser preciso sobre o que está estabelecido e o que não está: está documentado que os clientes eram pré-visualização e circunscritos ao SharePoint Online, que o mecanismo de permissões deles é um serviço do SharePoint Online, e que a MSAL não é suportada. O que não está documentado em lado nenhum que tenhamos encontrado é um caminho on-premises suportado. Ausência de documentação não prova que nada funciona, mas uma solução em produção por um caminho não documentado, num farm que vai ter de patchear nos próximos nove anos, é uma decisão a tomar de olhos abertos.
O padrão que aguenta é um serviço seu. Uma API publicada onde a controla, protegida como entender, que fala com o Graph do lado do servidor e que a sua web part chama com SPHttpClient ou com um HttpClient simples. A gestão de tokens deixa de ser um problema do navegador, e a solução fica dentro do que é suportado.
Testes: o runner já vem na caixa
Parte-se quase sempre do princípio de que aqui não há runner de testes. O artigo de CI/CD da própria Microsoft é a origem dessa ideia, e vale a pena ler a frase com atenção: "A SharePoint Framework não fornece uma framework de testes por omissão (desde a 1.8.0)".
Desde a 1.8.0. Abaixo disso, fornece, e a versão que a Subscription Edition aceita está abaixo disso.
Na 1.5.1, o pacote de build @microsoft/sp-build-web depende do @microsoft/gulp-core-build-karma, que traz o Karma, o Mocha, o Chai, o Sinon, o sinon-chai, o karma-coverage, o instrumentador Istanbul e o PhantomJS. O gerador põe @types/chai e @types/mocha no projeto que escreve para si. Há uma tarefa gulp test à espera, documentada como "corre testes unitários, se existirem".
O que falta não é a maquinaria. É um ficheiro de configuração, e quaisquer testes. Isso não está dito em lado nenhum, por isso o gulp test parece avariado e conclui-se que não há nada.
Três coisas que nada avisa
A tarefa não faz nada enquanto não existir um ficheiro de configuração. Procura ./karma.config.js na raiz do projeto. Se o ficheiro não estiver lá, escreve um aviso e volta, e o build continua verde. Escreva-o uma vez:
gulp test --initkarma
Isso copia a configuração por omissão para o projeto, e a partir daí o gulp test corre mesmo.
O runner lê o compilado, não as fontes. O padrão por omissão é /.+\.test\.js?$/ aplicado à pasta lib. A tarefa gera temp/tests.js com um require.context de webpack sobre lib, e o Karma carrega esse único ficheiro. Portanto um teste escrito em src/webparts/recentDocuments/services/DocumentService.test.ts é encontrado como lib/webparts/recentDocuments/services/DocumentService.test.js depois de o TypeScript correr. Duas consequências: chame ao ficheiro .test.ts e mais nada (um .spec.ts é ignorado em silêncio, sem erro e sem aviso), e lembre-se de que uma pasta lib velha corre testes velhos. gulp clean sempre que um resultado o surpreender.
Testes a falhar não fazem falhar o build. O failBuildOnErrors é false por omissão, portanto um teste vermelho escreve um aviso e o build segue. O modo de produção é a exceção: com --ship, a falha para tudo. Vale a pena mudar essa omissão logo no primeiro dia, porque um aviso no meio de uma parede de saída de build é uma bateria de testes que ninguém lê. No gulpfile.js, antes do build.initialize(gulp):
build.karma.setConfig({ failBuildOnErrors: true });
O teste
O Karma carrega as frameworks mocha e sinon-chai, portanto o describe e o it são globais e o Chai está lá para importar. Nada precisa de renderizador, porque nada do que vale a pena testar aqui renderiza.
// src/webparts/recentDocuments/services/DocumentService.test.ts
import { expect } from 'chai';
import { itemsUrl, describeStatus } from './DocumentService';
describe('itemsUrl', () => {
// Everybody develops against a list called Documents. Production has
// "HR & Payroll" and "Q1 'draft' items", and that is where this breaks.
it('doubles a single quote, which OData would read as the end of the title', () => {
expect(itemsUrl('https://sp/sites/hr', "Q1 'draft' items", 5))
.to.contain("getbytitle('Q1%20''draft''%20items')");
});
it('encodes an ampersand, which would otherwise start a new parameter', () => {
expect(itemsUrl('https://sp/sites/hr', 'HR & Payroll', 5))
.to.contain("getbytitle('HR%20%26%20Payroll')");
});
it('asks only for the three fields it renders', () => {
expect(itemsUrl('https://sp', 'Documents', 5))
.to.contain('$select=Id,Title,Modified');
});
});
describe('describeStatus', () => {
it('separates the failures a person can act on', () => {
expect(describeStatus(401)).to.equal('notAuthenticated');
expect(describeStatus(403)).to.equal('accessDenied');
expect(describeStatus(404)).to.equal('notFound');
});
it('reads a busy farm as throttled rather than broken', () => {
expect(describeStatus(429)).to.equal('throttled');
expect(describeStatus(503)).to.equal('throttled');
});
it('does not pretend to recognise a status it has never seen', () => {
expect(describeStatus(500)).to.equal('unknown');
});
});
gulp test # once, with coverage
gulp test --match itemsUrl # passed through to mocha as a grep
gulp test --debug # leaves the browser open, skips instrumentation
O PhantomJS, que é a parte que o vai travar mesmo
A configuração por omissão lista exatamente um navegador, e é o PhantomJS, cujo desenvolvimento foi suspenso em 2018.
O phantomjs-prebuilt descarrega um binário na instalação, a partir de uma tabela com quatro entradas: linux x64, linux ia32, darwin e win32. Qualquer outra coisa recebe "não existe binário disponível para a sua plataforma ou arquitetura" e a instalação falha. Repare no que ali significa darwin, porque é o que parece funcionar e não funciona: há uma única build para macOS e é x86_64, por isso em Apple Silicon o download passa e recebe-se um binário Intel que precisa de Rosetta. Em Linux arm64 não há entrada nenhuma.
O servidor de download é um release do GitHub, e é substituível:
PHANTOMJS_CDNURL=https://your-mirror/phantomjs npm install
É o mesmo problema das máquinas desligadas que apareceu atrás neste artigo, e tem a mesma forma: mais uma coisa para espelhar, orçada com honestidade em vez de descoberta na manhã do build.
Se o PhantomJS não for viável, e depois de 2018 normalmente não é, mude o navegador, não a framework. O Mocha, o Chai e todos os testes acima ficam exatamente como estão; muda apenas o launcher no karma.config.js, para karma-chrome-launcher com ChromeHeadless. Tenha presente que o Karma aqui está na 0.13, portanto qual a versão de launcher que colabora é coisa que se descobre a tentar, não a ler. É uma tarde contida e não toca numa linha do código de teste.
O outro caminho, se preferir deixar o navegador inteiramente de fora, é o Jest, para o qual o artigo de CI/CD da Microsoft aponta diretamente e para onde o SPFx foi de qualquer maneira a partir da 1.8.0. Para um projeto em React 15, essa documentação nomeia o preset: @voitanos/jest-preset-spfx-react15. Corre em Node, portanto o PhantomJS deixa de ser um problema seu, ao custo de uma segunda cadeia de ferramentas ao lado do gulp test.
O que testar, e o que não
Ganha o seu lugar:
- A construção do URL. Uma plica, um e comercial, um espaço, um acento. O teste de maior valor da solução inteira, e são os três casos acima.
- O mapeamento de estados. Um 403 dá um estado diferente de um 500, e um 404 diferente dos dois.
- A leitura da resposta. Um campo em falta, uma data que não converte, uma lista vazia.
Não ganha o seu lugar: afirmar que o React renderizou um <ul>.
E uma disciplina que importa mais do que tudo isto: parta cada guarda de propósito e confirme que um teste fica vermelho. Apague a flag _mounted, ou a comparação do componentDidUpdate, e corra a bateria. Se continuar tudo a passar, aprendeu uma coisa real: ou aquela guarda não está testada, ou o cenário não a exercita, ou o teste está a medir a propriedade errada. Uma bateria verde que continua verde depois de se sabotar o código não está a dizer que o código está certo. Não está a dizer nada.
Acessibilidade
O React 15 renderiza o mesmo HTML que o React 19. Nada disto depende da sua versão.
- Uma lista de resultados é um
<ul>de<li>, não uma pilha de<div>. - A ligação leva o título, para que quem navega por ligações ouça nomes de documentos em vez de "ler mais" repetido.
- As mudanças de estado vão para uma live region, que é o que o
role="status"e orole="alert"do componente acima estão a fazer. - Todos os controlos se alcançam por teclado e o foco vê-se.
- Nada é transmitido só pela cor. Um estado precisa de uma palavra.
Confirme na árvore de acessibilidade do navegador, e não a olho. O que um leitor de ecrã lê nem sempre é o que a marcação sugere.
Menos dependências, e porque pesa mais aqui
Cada biblioteca que se acrescenta fica presa a um TypeScript antigo e a um React antigo, queira-se ou não, e fica lá presa. Num tenant atualiza-se em silêncio; num farm, uma janela de atualização é um pedido de mudança com um fim de semana de manutenção agarrado.
Antes de acrescentar seja o que for, pergunte se o SharePoint já faz aquilo. Um gráfico é muitas vezes melhor como um número com uma barra proporcional. Um PDF costuma ser melhor como a impressão do próprio navegador com uma folha de estilos de impressão, que ainda por cima dá texto selecionável.
A medição que resolve a discussão é o tamanho do pacote num build limpo, antes e depois.
Mantenha o painel de propriedades aborrecido
Duas perguntas decidem se uma definição pertence ali.
Quem acrescenta a web part define isto uma vez, ou o leitor muda-o enquanto usa? Se for a segunda, pertence à interface.
Muda o comportamento? Se uma propriedade é aceite e ignorada, não é um lugar guardado para uma versão futura. É um defeito que alguém vai reportar, depois de ter confiado nela.
Quatro propriedades que fazem todas alguma coisa valem mais do que doze que quase nenhuma faz, em qualquer versão de qualquer coisa.
Publicação
O catálogo de aplicações do seu próprio farm, e o workbench em /_layouts/workbench.aspx num site que controle. Esse workbench não é o alojado que está a ser retirado no SharePoint Online; o seu vive enquanto o farm viver.
Duas coisas que vale a pena meter na rotina desde o início. Versione o .sppkg deliberadamente no package-solution.json em vez de o deixar em 1.0.0.0 para sempre, porque um catálogo de aplicações que mostra a mesma versão para três builds diferentes é um problema de suporte à espera de acontecer. E mantenha um registo de qual build está publicada onde, já que não há serviço nenhum a dizer-lho.
O resumo
Uma pista longa sobre uma base parada não é mau sítio para se estar. Recompensa decisões que são baratas agora e caras depois: pôr as chamadas num serviço, mapear os erros uma vez, tirar as cores do site, guardar as atualizações de estado, testar as partes puras, acrescentar o menos possível.
Uma web part escrita assim na 1.5.1 ainda estará a funcionar quando a janela de suporte da plataforma fechar, em 2035. Uma escrita da outra maneira não sobrevive à primeira mudança de tema, e é a si que cabe mantê-la nos dois casos.
Antes de começar
- Confirme o nível de atualização de funcionalidades do farm e verifique o teto de SPFx publicando um pacote trivial, em vez de confiar numa tabela
- Veja se o farm está na 23H2 ou posterior, porque é isso que decide se tem React 16 e hooks, e fixe a versão do React de qualquer maneira
- Fixe e registe no repositório as versões do gerador, do Node, do gulp-cli e do Yeoman
- Planeie a questão do acesso ao npm antes do primeiro dia, se as máquinas estiverem desligadas
- Ponha todas as chamadas num serviço, e mapeie os estados HTTP num só sítio
- Escape as plicas e codifique os títulos de lista em todos os URL de REST
- Guarde as atualizações de estado com uma flag de montagem e um contador de pedidos
- Compare as props antes de recarregar no
componentDidUpdate - Use tokens de tema em SCSS, nunca um valor hexadecimal
- Desenhe um estado para cada falha, incluindo a de resposta vazia
- Corra
gulp test --initkarmauma vez, chame.test.tsaos ficheiros de teste, e ponhafailBuildOnErrors: true - Resolva a questão do PhantomJS antes de escrever testes, trocando o launcher ou passando para o Jest
- Teste a construção do URL, o mapeamento de estados e a leitura da resposta, e parta cada guarda para provar que o teste fica vermelho
- Fixe o PnPjs numa versão que suporte o seu SPFx, e trate esse pin como permanente
- Versione o pacote deliberadamente e registe o que está publicado onde


